构造符号的双重发射 [英] Dual emission of constructor symbols

查看:128
本文介绍了构造符号的双重发射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

今天,我发现了一个有趣的事情, g ++ nm ...构造函数定义似乎有图书馆中有两个条目。



我有一个标题 thing.hpp

  class Thing 
{
Thing();

Thing(int x);

void foo();
};

thing.cpp

  #includething.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



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




  • 为什么定义的构造函数列出了两次?

  • 为什么没有这项事业符号的多个定义__的问题?






EDIT :对于Carl, c $ c> C 参数:

 %&纳米-g libthing.a 
0000000000000030牛逼_ZN5Thing3fooEv
0000000000000022牛逼_ZN5ThingC1Ei
000000000000000a牛逼_ZN5ThingC1Ev
0000000000000014牛逼_ZN5ThingC2Ei
0000000000000000牛逼_ZN5ThingC2Ev
ü__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)
休假

.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)
休假
$ RET b $ b .cfi_endproc

所以生成的代码是... ... ...同样。 / p>




EDIT :为了查看实际调用的构造函数,我更改了 Thing :: foo()到此:

  void Thing :: foo b {
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()的名称很容易parsed:

  _Z | N | 5Thing | 3foo | E | v 
prefix |嵌套| Thing| `foo` |结束嵌套|参数:`void`

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

 code> _Z | N | 5Thing | C1 | E | i 
prefix |嵌套| Thing|构造函数|结束嵌套|参数:`int`






c $ c> C1 ?您的副本具有 C2 是什么意思?



好吧,这也很简单

 < ctor-dtor-name> :: = C1#complete object constructor 
:: = C2#基础对象构造函数
:: = C3#完成对象分配构造函数
:: = D0#deleting destructor
:: = D1#complete object destructor
:: = D2#base object destructor






等待,为什么这个很简单?这个类没有基数。为什么每个都有一个完整的对象构造函数一个基础对象构造函数?




  • p> 这个Q& A 暗示我这只是一个


  • 请注意, c ++ filt 用于将此信息包含在其解析的输出中,但不再


  • 这个论坛帖子询问同一个问题,唯一的响应没有做任何更好的回答,除了暗示GCC 可以避免发出两个构造函数,当多态性不涉及,并且这种行为今后应该得到改善。


  • 此新闻组发布描述了由于此双重发射在构造函数中设置断点的问题。




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


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



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




  • 完整的对象构造函数/析构函数。

  • 基础对象构造函数/析构函数。

  • 分配构造函数/释放析构函数。



前两者不同,当虚拟基类是
时。 / p>






这些不同构造函数的含义


$ b $ / $ / $ / $ / $ / $。 b

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


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


  • 分配对象构造函数。它执行一切完整的对象构造函数,加上它调用操作符new来实际分配内存... 但显然这是不常见的。





如果你没有虚拟基类,[前两个]是
相同; GCC将在充分的优化级别上,实际上将
的符号标记为相同的代码。



Today, I discovered a rather interesting thing about either g++ or nm...constructor definitions appear to have two entries in libraries.

I have a header thing.hpp:

class Thing
{
    Thing();

    Thing(int x);

    void foo();
};

And thing.cpp:

#include "thing.hpp"

Thing::Thing()
{ }

Thing::Thing(int x)
{ }

void Thing::foo()
{ }

I compile this with:

g++ thing.cpp -c -o libthing.a

Then, I run nm on it:

%> 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

As you can see, both of the constructors for Thing are listed with two entries in the generated static library. My g++ is 4.4.3, but the same behavior happens in clang, so it isn't just a gcc issue.

This doesn't cause any apparent problems, but I was wondering:

  • Why are defined constructors listed twice?
  • Why doesn't this cause "multiple definition of symbol __" problems?

EDIT: For Carl, the output without the C argument:

%> nm -g libthing.a
0000000000000030 T _ZN5Thing3fooEv
0000000000000022 T _ZN5ThingC1Ei
000000000000000a T _ZN5ThingC1Ev
0000000000000014 T _ZN5ThingC2Ei
0000000000000000 T _ZN5ThingC2Ev
                 U __gxx_personality_v0

As you can see...the same function is generating multiple symbols, which is still quite curious.

And while we're at it, here is a section of generated assembly:

.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

So the generated code is...well...the same.


EDIT: To see what constructor actually gets called, I changed Thing::foo() to this:

void Thing::foo()
{
    Thing t;
}

The generated assembly is:

.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

So it is invoking the complete object constructor.

解决方案

We'll start by declaring that GCC follows the Itanium C++ ABI.


According to the ABI, the mangled name for your Thing::foo() is easily parsed:

_Z     | N      | 5Thing  | 3foo | E          | v
prefix | nested | `Thing` | `foo`| end nested | parameters: `void`

You can read the constructor names similarly, as below. Notice how the constructor "name" isn't given, but instead a C clause:

_Z     | N      | 5Thing  | C1          | E          | i
prefix | nested | `Thing` | Constructor | end nested | parameters: `int`


But what's this C1? Your duplicate has C2. What does this mean?

Well, 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


Wait, why is this simple? This class has no base. Why does it have a "complete object constructor" and a "base object constructor" for each?

  • This Q&A implies to me that this is simply a by-product of polymorphism support, even though it's not actually required in this case.

  • Note that c++filt used to include this information in its demangled output, but doesn't any more.

  • This forum post asks the same question, and the only response doesn't do any better at answering it, except for the implication that GCC could avoid emitting two constructors when polymorphism is not involved, and that this behaviour ought to be improved in the future.

  • This newsgroup posting describes a problem with setting breakpoints in constructors due to this dual-emission. It's stated again that the root of the issue is support for polymorphism.

In fact, 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.


The meaning of these different constructors seems to be as follows:

  • The "complete object constructor". It additionally constructs virtual base classes.

  • The "base object constructor". It creates the object itself, as well as data members and non-virtual base classes.

  • The "allocating object constructor". It does everything the complete object constructor does, plus it calls operator new to actually allocate the memory... but apparently this is not usually seen.

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.

这篇关于构造符号的双重发射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆