https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70868
Bug ID: 70868 Summary: Assigning constructed temporary of class with nontrivial constructor uses unnecessary temporary Product: gcc Version: 6.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: sethml at ofb dot net Target Milestone: --- It's a common C++ idiom to clear a struct by assigning a default-constructed instance to it: struct Foo1 { int x; int array[2000]; } foo1; void clearFoo1() { foo1 = Foo1(); } When compiled with -O2, this produces efficient assembly equivalent to "memset(&foo1, 0, sizeof(foo1))". However, throw in a nontrivial constructor via C++11 assignment initialization: struct Foo2 { int x = 0; int array[2000]; } foo2; void clearFoo2() { foo2 = Foo2(); } While compiled with -cstd=c++11 -O2, the resulting assembly allocates a temporary on the stack and copies it, equivalent to: void clearFoo2() { char temp[sizeof(foo2)]; memset(temp, 0, sizeof(foo2)); memcpy(foo2, temp, sizeof(foo2)); } Besides being inefficient, the resulting code uses far more stack than expected. In my case, my embedded firmware was crashing due to stack exhaustion! Here's a godbolt link with the code above: https://godbolt.org/g/QYLq9L The resulting x86-64 assembly is: clearFoo1(): movl $foo1, %edi movl $1000, %ecx xorl %eax, %eax rep stosq movl $0, (%rdi) ret clearFoo2(): subq $7904, %rsp xorl %eax, %eax movl $1000, %ecx leaq -120(%rsp), %rdi leaq -120(%rsp), %rsi rep stosq movl $1000, %ecx movl $0, (%rdi) movl $foo2, %edi rep movsq movl %eax, (%rdi) addq $7904, %rsp ret The result is the same with a variety of similar idioms: foo2 = Foo2(); foo2 = Foo2{}; foo2 = {}; The problem appears to have been in all old versions of gcc I've tested, up to 6.1.0. I haven't tested head. Clang >= 3.7 produces efficient code. Clang <= 3.6 produces a temporary and copy for both cases (clearFoo1 and clearFoo2). In my codebase, assigning a default-constructed object is a common way to clear a struct. Using memset() directly, besides being ugly, would fail to set nontrivial members and members with non-zero default values correctly. The best workaround I've found is to use placement new: void clearFoo2PlacementNew() { foo2.~Foo2(); new (&foo2) Foo2(); } Ugh.