重访c++中的构造&析构函数
来源:互联网 发布:ce引擎知乎 编辑:程序博客网 时间:2024/06/14 21:10
构造函数,析构函数,相信我们在写c++的时候,都是免不了的,但是最近我却有了新的发现。(下面的讨论都是基于linux 64bit,gcc 4.6)
这个应该是c++中面向对象中最简单的一个例子了吧。
虽说是个简单的例子,但是却有一个小问题,g++编译后,会有几个构造函数,几个析构函数呢?
当然如果我是这样问的,答案在大部分情况下应该是多个,呵呵。但是我又是怎么会想起来问这个问题的呢?主要是因为那天看到了stackoverflow上的帖子, http://stackoverflow.com/questions/6921295/dual-emission-of-constructor-symbols,
然后才有了研究这个问题的冲动,要是平时我也不会注意到编译器最后会多给你生成几个构造函数。
g++编译上面那个程序,
可以看到就这个简单的类,编译器就帮我多生成了一个构造函数跟一个析构函数。
以前知道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
这两段汇编除了指令地址,其他的一模一样,因为这里没有虚拟继承,构造函数C1跟构造函数C2相同。
那么有了虚拟继承又会怎样呢?
来看下面这个程序,菱形继承驾到。
同样的nm过后,我们看
对于父类A,C1 = C2。
看到B1::C1 != B1::C2
可以看到B1::C1履行了它complete construtor的职责,不管晚上成员变量初始化,还调用了它的父类A的C2构造函数。
而对于B1::C2则只进行了变量的赋值操作。B2::C1, B2::C2也类似,我就不把汇编贴出来了。
下面看看C::C1, C::C2
可以看到,C::C2并没有初始化它的虚基类A。
首先先看下面这段程序,
#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。
- 重访c++中的构造&析构函数
- 关于Object-c 中的构造函数与析构函数
- C++中的构造函数+拷贝构造函数+析构函数
- C语言中的构造函数
- 构造函数和析构函数【c++】
- c++-构造函数与析构函数
- C++-构造函数,析构函数
- [c++]构造函数和析构函数
- C++--构造函数与析构函数
- 【C#】构造函数和析构函数
- 【C++】构造函数和析构函数
- 【C++】构造函数和析构函数
- C++(构造函数&&析构函数)
- 【C#】构造函数&析构函数
- 【c++】构造函数与析构函数
- 【C++】类构造函数 & 析构函数
- C++:构造函数和析构函数
- C++:构造函数与析构函数
- 360的主页面的显示
- 360的单个标题按钮
- MFC 调用GDI库的虚线画笔绘图,放大到一定程度后变成实线解决方案
- 给师弟的一封信
- 360的标题按钮界面
- 重访c++中的构造&析构函数
- 我也谈谈日新网社区运营的一些东西
- C++中引用概念
- CTO如何避免决策失控(二):遭遇棘手问题的七种迹象
- 20130912安博培训第四天
- CTO都容易犯哪些错误
- Linux内核源代码分析工具
- jQuery UI Dialog 创建友好的弹出对话框实现代码 详细出处参考:http://www.jb51.net/article/30087.htm
- C++引用的概念