构造符号的双重发射

Tra*_*kel 100 c++ gcc constructor

今天,我发现了一个相当有趣的事情g++或者nm......构造函数定义似乎在库中有两个条目.

我有一个标题thing.hpp:

class Thing
{
    Thing();

    Thing(int x);

    void foo();
};
Run Code Online (Sandbox Code Playgroud)

而且thing.cpp:

#include "thing.hpp"

Thing::Thing()
{ }

Thing::Thing(int x)
{ }

void Thing::foo()
{ }
Run Code Online (Sandbox Code Playgroud)

我编译它:

g++ thing.cpp -c -o libthing.a
Run Code Online (Sandbox Code Playgroud)

然后,我继续nm:

%> nm -gC libthing.a
0000000000000030 T Thing::foo()
0000000000000022 T Thing::Thing(int)
000000000000000a T Thing::Thing()
0000000000000014 T Thing::Thing(int)
0000000000000000 T Thing::Thing()
                 U __gxx_personality_v0
Run Code Online (Sandbox Code Playgroud)

如您所见,两个构造函数Thing都在生成的静态库中列出了两个条目.我的g++是4.4.3,但是同样的行为发生在clang,所以这不仅仅是一个gcc问题.

这不会引起任何明显的问题,但我想知道:

  • 为什么定义的构造函数列出两次?
  • 为什么这不会导致"符号__的多重定义"问题?

编辑:对于卡尔,没有C参数的输出:

%> nm -g libthing.a
0000000000000030 T _ZN5Thing3fooEv
0000000000000022 T _ZN5ThingC1Ei
000000000000000a T _ZN5ThingC1Ev
0000000000000014 T _ZN5ThingC2Ei
0000000000000000 T _ZN5ThingC2Ev
                 U __gxx_personality_v0
Run Code Online (Sandbox Code Playgroud)

正如你所看到的......同样的功能是生成多个符号,这仍然很奇怪.

虽然我们在这里,但这里是生成组件的一部分:

.globl _ZN5ThingC2Ev
        .type   _ZN5ThingC2Ev, @function
_ZN5ThingC2Ev:
.LFB1:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movq    %rdi, -8(%rbp)
        leave
        ret
        .cfi_endproc
.LFE1:
        .size   _ZN5ThingC2Ev, .-_ZN5ThingC2Ev
        .align 2
.globl _ZN5ThingC1Ev
        .type   _ZN5ThingC1Ev, @function
_ZN5ThingC1Ev:
.LFB2:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movq    %rdi, -8(%rbp)
        leave
        ret
        .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

所以生成的代码是......好......相同.


编辑:要查看实际调用的构造函数,我改为Thing::foo():

void Thing::foo()
{
    Thing t;
}
Run Code Online (Sandbox Code Playgroud)

生成的程序集是:

.globl _ZN5Thing3fooEv
        .type   _ZN5Thing3fooEv, @function
_ZN5Thing3fooEv:
.LFB550:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        subq    $48, %rsp
        movq    %rdi, -40(%rbp)
        leaq    -32(%rbp), %rax
        movq    %rax, %rdi
        call    _ZN5ThingC1Ev
        leaq    -32(%rbp), %rax
        movq    %rax, %rdi
        call    _ZN5ThingD1Ev
        leave
        ret
        .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

所以它调用完整的对象构造函数.

Lig*_*ica 140

我们首先声明GCC遵循 Itanium C++ ABI.


根据ABI,您的错误名称Thing::foo()很容易解析:

_Z     | N      | 5Thing  | 3foo | E          | v
prefix | nested | `Thing` | `foo`| end nested | parameters: `void`
Run Code Online (Sandbox Code Playgroud)

您可以类似地读取构造函数名称,如下所示.请注意如何给出构造函数"name",而不是一个C子句:

_Z     | N      | 5Thing  | C1          | E          | i
prefix | nested | `Thing` | Constructor | end nested | parameters: `int`
Run Code Online (Sandbox Code Playgroud)

但是这是什么C1?你的副本有C2.这什么意思

嗯,这也很简单:

  <ctor-dtor-name> ::= C1   # complete object constructor
                   ::= C2   # base object constructor
                   ::= C3   # complete object allocating constructor
                   ::= D0   # deleting destructor
                   ::= D1   # complete object destructor
                   ::= D2   # base object destructor
Run Code Online (Sandbox Code Playgroud)

等等,为什么这么简单?这个班没有基础.为什么每个都有一个"完整的对象构造函数" "基础对象构造函数"?

  • 这个Q&A向我暗示,这只是多态支持的副产品,即使在这种情况下实际上并不需要.

  • 请注意,c++filt用于在其demangled输出中包含此信息,但不再包含此信息.

  • 此论坛发帖问同样的问题,唯一的反应完全不回答它,除了为GCC寓意做任何更好的避免排放两个构造函数时不参与多态,而且这种行为应该在今后加以改进.

  • 此新闻组发布描述了由于此双重发射而在构造函数中设置断点的问题.再次声明问题的根源是对多态性的支持.

事实上,这被列为GCC"已知问题":

G ++发出两个构造函数和析构函数的副本.

通常,有三种类型的构造函数(和析构函数).

  • 完整的对象构造函数/析构函数.
  • 基础对象构造函数/析构函数.
  • 分配构造函数/解除分配析构函数.

当涉及虚拟基类时,前两个是不同的.


这些不同构造函数的含义似乎如下:

  • "完整的对象构造函数".它还构造了虚拟基类.

  • "基础对象构造函数".它创建对象本身,以及数据成员和非虚拟基类.

  • "分配对象构造函数".它完成了完整对象构造函数所做的一切,并且它调用operator new来实际分配内存...... 但显然这通常不会被看到.

如果你没有虚拟基类,[前两个]是相同的; 在足够的优化级别上,GCC实际上将符号别名为两者的相同代码.

  • @Tomalak Geret'kal:+1,对于回答Q的非常详细的研究. (6认同)
  • 万岁的答案 - 我想我正在接受这个,但看到正确的信息是很好的. (4认同)
  • 这是一个很棒的答案,但是有没有这些构造函数类型之间的区别的文档?主要是:什么是"分配构造函数"和"删除析构函数"?它们是否用于重载`operator new`和`operator delete`? (4认同)