重访c++中的构造&析构函数

来源:互联网 发布:ce引擎知乎 编辑:程序博客网 时间:2024/06/14 21:10
构造函数,析构函数,相信我们在写c++的时候,都是免不了的,但是最近我却有了新的发现。(下面的讨论都是基于linux 64bit,gcc 4.6)

首先先看下面这段程序,


#include <iostream>                                                         using namespace std;                                                        class A                               {                                     public:                                   A(int i);                             ~A();                                 void show();                      private:                                  int a;                            };                                                                          A::A(int i) : a(i)                    {}                                                                          A::~A()                               {}                                                                          void                                  A::show()                             {                                         cout << "A::show:" << a << endl;  }                                                                           int main()                            {                                         return 0;                         }        

这个应该是c++中面向对象中最简单的一个例子了吧。
虽说是个简单的例子,但是却有一个小问题,g++编译后,会有几个构造函数,几个析构函数呢?
当然如果我是这样问的,答案在大部分情况下应该是多个,呵呵。但是我又是怎么会想起来问这个问题的呢?主要是因为那天看到了stackoverflow上的帖子,    http://stackoverflow.com/questions/6921295/dual-emission-of-constructor-symbols,
然后才有了研究这个问题的冲动,要是平时我也不会注意到编译器最后会多给你生成几个构造函数。


g++编译上面那个程序,   

nm a.out00000000004008e4 T _ZN1A4showEv00000000004008ba T _ZN1AC1Ei00000000004008a4 T _ZN1AC2Ei00000000004008da T _ZN1AD1Ev00000000004008d0 T _ZN1AD2Ev

可以看到就这个简单的类,编译器就帮我多生成了一个构造函数跟一个析构函数。
以前知道c++ name mangling,但是对具体的东西一些规则还不是很了解,在上面的stackoverflow中给出了比较详细的介绍,我就在这里再复制粘贴下吧。
因为GCC遵守的是Itanium C++ API,所以对于c++中的成员函数Thing::foo()的重命名就可以按照下面的这个顺序解释:
_Z     | N      | 5Thing  | 3foo | E          | v
prefix | nested | `Thing` | `foo`| end nested | parameters: `void`



下面是对于构造函数的解析
_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
--------------------------------------------------------------------------------


在原帖说到,这个实现是G++的known issue,http://gcc.gnu.org/bugs/#known,
注意是known bug issue哦。说不定以后会在哪个版本中不是这样了。


他们的不同在于:
complete object constructor: 它会把继承关系中所有的类都初始化,包括虚基类。
base object constructor: 创建对象,但是对虚基类不做初始化。
allocating object constructor: complete object constructor做的事情它都做,另外它还会调用operator new来分配内存(我不清楚这个的使用场合)。


好了,下面我们就来探索一下吧。


还是上面那个简单的程序,把两个构造函数的汇编代码拉出来
00000000004008ba T _ZN1AC1Ei
00000000004008a4 T _ZN1AC2Ei


(gdb) disassemble _ZN1AC1EiDump of assembler code for function A:0x00000000004008ba <A+0>:       push   %rbp0x00000000004008bb <A+1>:       mov    %rsp,%rbp0x00000000004008be <A+4>:       mov    %rdi,-0x8(%rbp)0x00000000004008c2 <A+8>:       mov    %esi,-0xc(%rbp)0x00000000004008c5 <A+11>:      mov    -0x8(%rbp),%rax0x00000000004008c9 <A+15>:      mov    -0xc(%rbp),%edx0x00000000004008cc <A+18>:      mov    %edx,(%rax)0x00000000004008ce <A+20>:      leaveq0x00000000004008cf <A+21>:      retqEnd of assembler dump.(gdb) disassemble _ZN1AC2EiDump of assembler code for function A:0x00000000004008a4 <A+0>:       push   %rbp0x00000000004008a5 <A+1>:       mov    %rsp,%rbp0x00000000004008a8 <A+4>:       mov    %rdi,-0x8(%rbp)0x00000000004008ac <A+8>:       mov    %esi,-0xc(%rbp)0x00000000004008af <A+11>:      mov    -0x8(%rbp),%rax0x00000000004008b3 <A+15>:      mov    -0xc(%rbp),%edx0x00000000004008b6 <A+18>:      mov    %edx,(%rax)0x00000000004008b8 <A+20>:      leaveq0x00000000004008b9 <A+21>:      retqEnd of assembler dump.

这两段汇编除了指令地址,其他的一模一样,因为这里没有虚拟继承,构造函数C1跟构造函数C2相同。


那么有了虚拟继承又会怎样呢?
来看下面这个程序,菱形继承驾到。



#include <iostream>                                                                                                   using namespace std;                                                                                                  class A                                                    {                                                          public:                                                        A();                                                       ~A();                                                      void show() {cout << "A::show():" << a << endl;}       private:                                                       int a;                                                 };                                                                                                                    A::A() : a(10)                                             {}                                                         A::~A()                                                    {}                                                                                                                    class B1 : public virtual A                                {                                                          public:                                                        B1();                                                      ~B1();                                                     int b1;                                                };                                                                                                                    B1::B1() : b1(11)                                          {}                                                         B1::~B1()                                                  {}                                                         class B2 : public virtual A                                {                                                          public:                                                        B2();                                                      ~B2();                                                     int b2;                                                };                                                                                                                    B2::B2() : b2(12)                                          {}                                                         B2::~B2()                                                  {}                                                class C : public B1, public B2     {                                  public:                                C();                               ~C();                              int c;                         };                                                                    C::C() : c(20)                     {}                                 C::~C()                            {}                                                                    int main()                         {                                      return 0;                      }                                           

同样的nm过后,我们看

0000000000400788 T _ZN1AC1Ev0000000000400774 T _ZN1AC2Ev00000000004007a6 T _ZN1AD1Ev000000000040079c T _ZN1AD2Ev00000000004009c6 T _ZN1CC1Ev0000000000400950 T _ZN1CC2Ev0000000000400ac0 T _ZN1CD1Ev0000000000400a3c T _ZN1CD2Ev00000000004007da T _ZN2B1C1Ev00000000004007b0 T _ZN2B1C2Ev000000000040084c T _ZN2B1D1Ev0000000000400810 T _ZN2B1D2Ev00000000004008aa T _ZN2B2C1Ev0000000000400880 T _ZN2B2C2Ev000000000040091c T _ZN2B2D1Ev00000000004008e0 T _ZN2B2D2Ev

看到也是只用了C1,C2,那么这种情况下,C1,C2会跟上面一样也是同样的操作吗?

还是拉汇编吧,


(gdb) disassemble _ZN1AC1EvDump of assembler code for function A:0x0000000000400788 <A+0>:       push   %rbp0x0000000000400789 <A+1>:       mov    %rsp,%rbp0x000000000040078c <A+4>:       mov    %rdi,-0x8(%rbp)0x0000000000400790 <A+8>:       mov    -0x8(%rbp),%rax0x0000000000400794 <A+12>:      movl   $0xa,(%rax)0x000000000040079a <A+18>:      leaveq0x000000000040079b <A+19>:      retqEnd of assembler dump.(gdb) disassemble _ZN1AC2EvDump of assembler code for function A:0x0000000000400774 <A+0>:       push   %rbp0x0000000000400775 <A+1>:       mov    %rsp,%rbp0x0000000000400778 <A+4>:       mov    %rdi,-0x8(%rbp)0x000000000040077c <A+8>:       mov    -0x8(%rbp),%rax0x0000000000400780 <A+12>:      movl   $0xa,(%rax)    <<<===初始化为100x0000000000400786 <A+18>:      leaveq0x0000000000400787 <A+19>:      retqEnd of assembler dump.



对于父类A,C1 = C2。

(gdb) disassemble _ZN2B1C1EvDump of assembler code for function B1:0x00000000004007da <B1+0>:      push   %rbp0x00000000004007db <B1+1>:      mov    %rsp,%rbp0x00000000004007de <B1+4>:      sub    $0x8,%rsp0x00000000004007e2 <B1+8>:      mov    %rdi,-0x8(%rbp)0x00000000004007e6 <B1+12>:     mov    -0x8(%rbp),%rax0x00000000004007ea <B1+16>:     add    $0xc,%rax0x00000000004007ee <B1+20>:     mov    %rax,%rdi0x00000000004007f1 <B1+23>:     callq  0x400774 <A>  <<<===对照上面,这个是A::C20x00000000004007f6 <B1+28>:     mov    $0x400d98,%edx0x00000000004007fb <B1+33>:     mov    -0x8(%rbp),%rax0x00000000004007ff <B1+37>:     mov    %rdx,(%rax)0x0000000000400802 <B1+40>:     mov    -0x8(%rbp),%rax0x0000000000400806 <B1+44>:     movl   $0xb,0x8(%rax) <<<=== 初始化为110x000000000040080d <B1+51>:     leaveq0x000000000040080e <B1+52>:     retqEnd of assembler dump.(gdb) disassemble _ZN2B1C2EvDump of assembler code for function B1:0x00000000004007b0 <B1+0>:      push   %rbp0x00000000004007b1 <B1+1>:      mov    %rsp,%rbp0x00000000004007b4 <B1+4>:      mov    %rdi,-0x8(%rbp)0x00000000004007b8 <B1+8>:      mov    %rsi,-0x10(%rbp)0x00000000004007bc <B1+12>:     mov    -0x10(%rbp),%rax0x00000000004007c0 <B1+16>:     mov    (%rax),%rax0x00000000004007c3 <B1+19>:     mov    %rax,%rdx0x00000000004007c6 <B1+22>:     mov    -0x8(%rbp),%rax0x00000000004007ca <B1+26>:     mov    %rdx,(%rax)0x00000000004007cd <B1+29>:     mov    -0x8(%rbp),%rax0x00000000004007d1 <B1+33>:     movl   $0xb,0x8(%rax)0x00000000004007d8 <B1+40>:     leaveq0x00000000004007d9 <B1+41>:     retqEnd of assembler dump.

看到B1::C1 != B1::C2
可以看到B1::C1履行了它complete construtor的职责,不管晚上成员变量初始化,还调用了它的父类A的C2构造函数。
而对于B1::C2则只进行了变量的赋值操作。B2::C1, B2::C2也类似,我就不把汇编贴出来了。


下面看看C::C1, C::C2


(gdb) disassemble _ZN1CC1EvDump of assembler code for function C:0x00000000004009c6 <C+0>:       push   %rbp0x00000000004009c7 <C+1>:       mov    %rsp,%rbp0x00000000004009ca <C+4>:       sub    $0x8,%rsp0x00000000004009ce <C+8>:       mov    %rdi,-0x8(%rbp)0x00000000004009d2 <C+12>:      mov    -0x8(%rbp),%rax0x00000000004009d6 <C+16>:      add    $0x20,%rax0x00000000004009da <C+20>:      mov    %rax,%rdi0x00000000004009dd <C+23>:      callq  0x400774 <A>   <<<=== A::C20x00000000004009e2 <C+28>:      mov    $0x400d00,%eax0x00000000004009e7 <C+33>:      lea    0x8(%rax),%rdx0x00000000004009eb <C+37>:      mov    -0x8(%rbp),%rax0x00000000004009ef <C+41>:      mov    %rdx,%rsi0x00000000004009f2 <C+44>:      mov    %rax,%rdi0x00000000004009f5 <C+47>:      callq  0x4007b0 <B1>   <<<===B1::C20x00000000004009fa <C+52>:      mov    $0x400d00,%eax0x00000000004009ff <C+57>:      lea    0x10(%rax),%rdx0x0000000000400a03 <C+61>:      mov    -0x8(%rbp),%rax0x0000000000400a07 <C+65>:      add    $0x10,%rax0x0000000000400a0b <C+69>:      mov    %rdx,%rsi0x0000000000400a0e <C+72>:      mov    %rax,%rdi0x0000000000400a11 <C+75>:      callq  0x400880 <B2>  <<<===B2::C20x0000000000400a16 <C+80>:      mov    $0x400cd8,%edx0x0000000000400a1b <C+85>:      mov    -0x8(%rbp),%rax0x0000000000400a1f <C+89>:      mov    %rdx,(%rax)0x0000000000400a22 <C+92>:      mov    $0x400cf0,%edx0x0000000000400a27 <C+97>:      mov    -0x8(%rbp),%rax0x0000000000400a2b <C+101>:     mov    %rdx,0x10(%rax)0x0000000000400a2f <C+105>:     mov    -0x8(%rbp),%rax0x0000000000400a33 <C+109>:     movl   $0x14,0x1c(%rax)0x0000000000400a3a <C+116>:     leaveq0x0000000000400a3b <C+117>:     retqEnd of assembler dump.(gdb) disassemble _ZN1CC2EvDump of assembler code for function C:0x0000000000400950 <C+0>:       push   %rbp0x0000000000400951 <C+1>:       mov    %rsp,%rbp0x0000000000400954 <C+4>:       sub    $0x10,%rsp0x0000000000400958 <C+8>:       mov    %rdi,-0x8(%rbp)0x000000000040095c <C+12>:      mov    %rsi,-0x10(%rbp)0x0000000000400960 <C+16>:      mov    -0x10(%rbp),%rax0x0000000000400964 <C+20>:      lea    0x8(%rax),%rdx0x0000000000400968 <C+24>:      mov    -0x8(%rbp),%rax0x000000000040096c <C+28>:      mov    %rdx,%rsi0x000000000040096f <C+31>:      mov    %rax,%rdi0x0000000000400972 <C+34>:      callq  0x4007b0 <B1>    <<<== B1::C20x0000000000400977 <C+39>:      mov    -0x10(%rbp),%rax0x000000000040097b <C+43>:      lea    0x10(%rax),%rdx0x000000000040097f <C+47>:      mov    -0x8(%rbp),%rax0x0000000000400983 <C+51>:      add    $0x10,%rax0x0000000000400987 <C+55>:      mov    %rdx,%rsi0x000000000040098a <C+58>:      mov    %rax,%rdi0x000000000040098d <C+61>:      callq  0x400880 <B2>    <<<=== B2::C20x0000000000400992 <C+66>:      mov    -0x10(%rbp),%rax0x0000000000400996 <C+70>:      mov    (%rax),%rax0x0000000000400999 <C+73>:      mov    %rax,%rdx0x000000000040099c <C+76>:      mov    -0x8(%rbp),%rax0x00000000004009a0 <C+80>:      mov    %rdx,(%rax)0x00000000004009a3 <C+83>:      mov    -0x10(%rbp),%rax0x00000000004009a7 <C+87>:      add    $0x18,%rax0x00000000004009ab <C+91>:      mov    (%rax),%rax0x00000000004009ae <C+94>:      mov    %rax,%rdx0x00000000004009b1 <C+97>:      mov    -0x8(%rbp),%rax0x00000000004009b5 <C+101>:     mov    %rdx,0x10(%rax)0x00000000004009b9 <C+105>:     mov    -0x8(%rbp),%rax0x00000000004009bd <C+109>:     movl   $0x14,0x1c(%rax)0x00000000004009c4 <C+116>:     leaveq0x00000000004009c5 <C+117>:     retqEnd of assembler dump.

可以看到,C::C2并没有初始化它的虚基类A。

原创粉丝点击