This is the mail archive of the
gcc-bugs@gcc.gnu.org
mailing list for the GCC project.
[Bug c++/70868] New: Assigning constructed temporary of class with nontrivial constructor uses unnecessary temporary
- From: "sethml at ofb dot net" <gcc-bugzilla at gcc dot gnu dot org>
- To: gcc-bugs at gcc dot gnu dot org
- Date: Fri, 29 Apr 2016 06:35:10 +0000
- Subject: [Bug c++/70868] New: Assigning constructed temporary of class with nontrivial constructor uses unnecessary temporary
- Auto-submitted: auto-generated
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.