构造函数符号的双重发射
今天,我发现了关于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
问题。
这不会导致任何明显的问题,但我想知道:
- 为什么定义的构造函数列出两次?
- 为什么这不会导致“符号__的多重定义”问题?
编辑 :对于卡尔,没有C
参数的输出:
%> nm -g libthing.a 0000000000000030 T _ZN5Thing3fooEv 0000000000000022 T _ZN5ThingC1Ei 000000000000000a T _ZN5ThingC1Ev 0000000000000014 T _ZN5ThingC2Ei 0000000000000000 T _ZN5ThingC2Ev U __gxx_personality_v0
正如你所看到的…同样的function是产生多个符号,这仍然是很好奇。
而我们在这里,这是一个生成的程序集的一部分:
.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遵循 Itanium C ++ ABI 。
根据ABI, Thing::foo()
名字很容易被parsing:
_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
。 这是什么意思 ?
那么这也很简单 :
<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
等等,为什么这很简单 ? 这个class没有基地。 为什么每个对象都有一个“完全对象构造函数” 和一个“基础对象构造函数”?
-
这个问答给我意味着这只是多态支持的一个副产品,尽pipe在这种情况下实际上并不需要。
-
请注意,
c++filt
用于将这些信息包含在demangled输出中, 但是不再包含这些信息。 -
这个论坛post提出了同样的问题,唯一的回答在回答这个问题上并没有做得更好,除了GCC可以避免在没有涉及多态性时排除两个构造函数,并且这种行为应该在将来得到改进。
-
这个新闻组发布描述了由于这种双发射而在构造器中设置断点的问题。 再次说明问题的根源在于支持多态。
事实上, 这被列为海湾合作委员会的“已知问题” :
G ++发出构造函数和析构函数的两个副本。
通常有三种types的构造函数(和析构函数)。
- 完整的对象构造/析构函数。
- 基础对象构造函数/析构函数。
- 分配构造函数/释放析构函数。
前两个是不同的,当涉及虚拟基类。
这些不同构造函数的含义似乎如下 :
-
“完整的对象构造函数”。 它还构build了虚拟基类。
-
“基础对象构造函数”。 它创build对象本身,以及数据成员和非虚拟基类。
-
“分配对象构造函数”。 它完成了完整的对象构造函数所做的一切,再加上它调用操作符new来实际分配内存… 但显然这是不常见的。
如果你没有虚拟基类,[前两个]是相同的; GCC将在足够的优化级别上实际上将这些符号别名为相同的代码。