c++assemblycopy-constructormove-constructorcompiler-explorer

GCC does not generate machine code for out-of-class defaulted copy constructor


Assume the following source file (translation unit; TU):

struct X {
    int i;
    
    X(const X&);
    X(X&&);
};

X::X(const X&) = default;
X::X(X&&) = default

If I compile it with Clang, it generates the machine code for both copy and move constructors. However, GCC generates it for the move constructor only, and the machine code of the copy constructor is missing in the resulting object file. I first thought it was some problem related to the Compiler Explorer, but then I observed the same behavior on my local Linux system using objdump.

Live demo: https://godbolt.org/z/1re4brr6P

I don't understand this, because, once I have another TU with a copy constructor call, it needs to be called from the machine code: https://godbolt.org/z/3YaMTd5do. So, GCC generates a call of the copy constructor in the second TU, but does not generate its machine code in the first TU. How can then the linker link the object files together?


Solution

  • It is just a problem with how compiler explorer presents the output (as does objdump):

    GCC defines symbols for both constructors at the same address because the functions have the exact same behavior. The symbol table:

    $ nm test.o 
    0000000000000000 T _ZN1XC1EOS_
    0000000000000000 T _ZN1XC1ERKS_
    0000000000000000 T _ZN1XC2EOS_
    0000000000000000 T _ZN1XC2ERKS_
    
    $ nm -C test.o
    0000000000000000 T X::X(X&&)
    0000000000000000 T X::X(X const&)
    0000000000000000 T X::X(X&&)
    0000000000000000 T X::X(X const&)
    

    Usually a compiler can't optimize two functions to have the exact same address and would instead compile one of the identical functions to a single jump instruction into the other one, but for a constructor (and other non-static (implicit-object) member functions) that is ok, because there is no way to observe the address of a constructor in C++.

    In compiler explorer, if you are looking at the assembly level rather than the disassembled binary, you can also disable the filtering of assembler directives via the third icon from the left in the compiler window. Then you will see directives that cause all of these symbols to be defined at the same location. Unfortunately there will be many directives, so it gets a bit hard to read. This will not be possible in the "Compile to binary object"/"Link to binary" modes.