Daniel Langr
Daniel Langr

Reputation: 23527

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?

Upvotes: 2

Views: 158

Answers (2)

Marek R
Marek R

Reputation: 38181

This is result of different optimizations used by compilers at -O2. So basically those to function has been merged as one since resulting machine code is identical.

Using the GNU Compiler Collection (GCC): Optimize Options

-fipa-icf

Perform Identical Code Folding for functions and read-only variables. The optimization reduces code size and may disturb unwind stacks by replacing a function by equivalent one with a different name. The optimization works more effectively with link-time optimization enabled.

Nevertheless the behavior is similar to Gold Linker ICF optimization, GCC ICF works on different levels and thus the optimizations are not same - there are equivalences that are found only by GCC and equivalences found only by Gold.

This flag is enabled by default at -O2 and -Os.

https://godbolt.org/z/6vP4fbfYW

Upvotes: 4

user17732522
user17732522

Reputation: 76859

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.

Upvotes: 10

Related Questions