2

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?

6
  • The machine code is generated to an object file for each TU. Including some information on the name of the function, the linker can then know which parts of the object file to link into the final exe. For more details : Back to Basics: Compiling and Linking - Ben Saks - CppCon 2021 Commented Aug 9, 2024 at 5:25
  • 1
    @PepijnKramer How can a linker link a function call to its machine code, when this machine code is missing? Sorry, but I don't understand how is your comment related to the asked problem. Could you specify which particular part of that talk explains it? Commented Aug 9, 2024 at 5:27
  • @DanielLangr You should also post the assembly here in your post. Commented Aug 9, 2024 at 5:32
  • 6
    gcc simply merges both constructors into one chunk of machine code. The symbol for the copy ctor is there, godbolt just filters it out. Use nm -C myfile.o locally to see it. Commented Aug 9, 2024 at 5:36
  • 3
    @n.m.couldbeanAI: you can also turn off directive filtering on Godbolt, GCC often uses .set foo, bar to define foo as having the same address as bar. Commented Aug 9, 2024 at 6:05

2 Answers 2

10

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.

Sign up to request clarification or add additional context in comments.

1 Comment

You can turn off directive filtering in the dropdown on Godbolt, GCC often uses .set foo, bar to define foo as having the same address as bar. Godbolt's default filter removes .set directives.
4

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.