c++ - 构造函数符号的双重发射

标签 c++ gcc constructor

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

我有一个标题 thing.hpp :

class Thing
{
    Thing();

    Thing(int x);

    void foo();
};

thing.cpp :
#include "thing.hpp"

Thing::Thing()
{ }

Thing::Thing(int x)
{ }

void Thing::foo()
{ }

我编译这个:
g++ thing.cpp -c -o libthing.a

然后,我运行 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

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

这不会导致任何明显的问题,但我想知道:
  • 为什么定义的构造函数会列出两次?
  • 为什么这不会导致“符号__的多重定义”问题?


  • 编辑 :对于 Carl,没有 C 的输出争论:
    %> nm -g libthing.a
    0000000000000030 T _ZN5Thing3fooEv
    0000000000000022 T _ZN5ThingC1Ei
    000000000000000a T _ZN5ThingC1Ev
    0000000000000014 T _ZN5ThingC2Ei
    0000000000000000 T _ZN5ThingC2Ev
                     U __gxx_personality_v0
    

    如您所见……同一个函数正在生成多个符号,这还是很奇怪的。

    当我们在做的时候,这里是生成的程序集的一部分:
    .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
    

    所以生成的代码是……嗯……是一样的。

    编辑 :为了查看实际调用的构造函数,我更改了 Thing::foo()对此:
    void Thing::foo()
    {
        Thing t;
    }
    

    生成的程序集是:
    .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
    

    所以它正在调用完整的对象构造函数。

    最佳答案

    我们首先声明 GCC follows the Itanium C++ ABI .

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

    _Z     | N      | 5Thing  | 3foo | E          | v
    prefix | nested | `Thing` | `foo`| end nested | parameters: `void`
    
    您可以类似地读取构造函数名称,如下所示。注意构造函数“name”是如何没有给出的,而是一个 C条款:
    _Z     | N      | 5Thing  | C1          | E          | i
    prefix | nested | `Thing` | Constructor | end nested | parameters: `int`
    

    但这是什么C1 ?您的拷贝有 C2 .这是什么意思?
    那么,this is quite simple too :
      <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
    

    等等,为什么这么简单?这个类没有基础。为什么它有一个“完整的对象构造函数”每个的“基本对象构造函数”?
  • This Q&A向我暗示这只是多态支持的副产品,即使在这种情况下实际上并不需要它。
  • 请注意 c++filt过去常常将此信息包含在其解构输出中,but doesn't any more .
  • This forum post问同样的问题,唯一的回答并没有更好地回答它,除了暗示 GCC 可以 当不涉及多态时避免发出两个构造函数,并且这种行为应该在 future 得到改进。
  • This newsgroup posting描述了由于这种双重发射而在构造函数中设置断点的问题。再次声明问题的根源是对多态性的支持。

  • 事实上,this is listed as a GCC "known issue" :

    G++ emits two copies of constructors and destructors.

    In general there are three types of constructors (and destructors).

    • The complete object constructor/destructor.
    • The base object constructor/destructor.
    • The allocating constructor/deallocating destructor.

    The first two are different, when virtual base classes are involved.



    这些不同构造函数的含义seems to be as follows :
  • “完整的对象构造函数”。它还构造了虚拟基类。
  • “基础对象构造函数”。它创建对象本身,以及数据成员和非虚拟基类。
  • “分配对象构造函数”。它完成了完整的对象构造函数所做的一切,此外它还调用 operator new 来实际分配内存……但显然这并不常见。

  • If you have no virtual base classes, [the first two] are are identical; GCC will, on sufficient optimization levels, actually alias the symbols to the same code for both.

    关于c++ - 构造函数符号的双重发射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45267549/

    相关文章:

    c++ - boost split 用一个字符或一个字符串

    gcc - 为什么clang++3.5编译不了这段代码,而clang++3.6可以编译呢?

    c++ - 如何将 C++11 std::stoi 与 gcc 一起使用?

    c++ - 在构造函数中传递const引用时,如何强制编译器不接受右值

    angularjs - 在 angular2 constructor() 中使用 private 与 public 关键字有什么不同

    c++ - Mac/Linux 上的 Windows 控制台应用程序? C++

    c++ - EXC_BAD_ACCESS 从 NSTimer 调用 c++ 类方法

    c++ - 组件-实体-系统 - 输入处理

    c++ - std::unordered_map 之间的reinterpret_cast

    C# 自动化构造函数重构