This is the mail archive of the gcc-bugs@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[Bug c++/70868] New: Assigning constructed temporary of class with nontrivial constructor uses unnecessary temporary


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.

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]