http://saturnman.blog.163.com/blog/static/557611201081421344244/
对于C++带于虚函数的单继承类的构造过程我原来写过一篇日志,感觉还有一些不够明确,这里通过调式器再看一下到底构造过程中发生了什么。
本文是通过对msvc和gcc这两大主流编译器的编译结果调式来探所在带于虚函数的单继承类的构造过程中发生的事情,对于其它编译器可能结果不与此相同,本文仅供参考,鉴于本文与我前一篇日志有些关联,如果能先读一下那篇日志可能对于理解本文有帮助,上篇地址如下:
http://saturnman.blog.163.com/blog/static/557611201062910383394/
#include
#include
#include
#include
using namespace std;
//Function to show binary info of an object
typedef unsigned char* pointer_type;
void show_byte(pointer_type pointer,int length);
void show_byte(pointer_type pointer,int length)
{
cout<<"address of object:"<m_pObj_Name);
}
private:
int flag_var;
string* m_pObj_Name;
virtual void pure_virtual_1() = 0;//pure virtual function
virtual void pure_virtual_2() = 0;//pure virtual function
virtual void virtual_func1() //normal virtual function
{
cout<<"virtual function in Base."<GetName()<Util();
b1->Util();
Derived* d2 = new(buff2) Derived("Derived_2");
Base* b2 = d2;
cout<<"After construct:"<GetName()<Util();
b2->Util();
d1->~Derived();
d2->~Derived();
return 0;
}
编译器版本如下:
gcc
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=c:/mingw32/bin/../libexec/gcc/mingw32/4.5.0/lto-wrapper.exe
Target: mingw32
Configured with: ../../gcc-4.5.0/configure --prefix=/mingw --build=mingw32 --ena
ble-languages=c,ada,c++,fortran,objc,obj-c++ --disable-nls --disable-win32-regis
try --enable-libgomp --disable-werror --enable-threads --disable-symvers --enabl
e-cxx-flags='-fno-function-sections -fno-data-sections' --enable-fully-dynamic-s
tring --enable-libstdcxx-debug --enable-version-specific-runtime-libs --with-pkg
version=tdm-1 --enable-sjlj-exceptions --with-bugurl=http://tdm-gcc.tdragon.net/
bugs
Thread model: win32
gcc version 4.5.0 (tdm-1)
msvc
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
为了便于理解本文,我们再回顾一下类的基本构造与销毁过程,可分为如下四步:1.allocate 2.construct 3.destruct 4.deallocate 对于这四步并不一定是连续的,可以先申请空间(allocate),在等待一定时机或是某个条件触发后,再进行类的构造,典型的例子stl的容器类,比如vector,对其调用reserve()函数后,其空间增大,但是并没有在空间上调用默认构构函数对容器元素进行构造,只有当push_back()之类的函数调用发生时再构造相应的函数,这样可以极大节约进行时开销。好,类的一般构造过程就回顾到这里,为了调式类的构造过程,我们在此使用了预先申请好的内存空间,之后利用new布局操作符把类布局到我们的空间上,这时类的构造过程就少了allocate和deallocate。如果你对new布局操作符不熟悉,可以找一本基础C++书看一下,一般都会有的,如果没有就换一本(记得把原来那本烧掉J)。这里使用new布局操作符是为了查看我们的内存发生了什么,来了解类构造的过程。
下图是在vs2010上断下的情形,先找到buff1的地址,之后把这个地址写到内存查看中,这样做是因为当buff1出了作用域时仍然可以监视内存的变化。
则开始,buff1的内存地址放入监视。
先跟进
由于我们的内存是事先分配好的,类只要布局上去就可以了,这个内联函数很简单,直接返回地址,其实在编译后这个函数不存在代码,只是一个地址,不要对此感到奇怪。
之后跳出来到下图
此时回到主函数,再跟进。
这是把参数字串构造成
string 对象的构造函数跳出。
又回到主函数
再跟进
子类构造函数【这里还没有运行子类构造函数体任何代码】
跟进
先调用基类构造函数,【这里还没有运行基类构造函数任何代码】
再跟进。
此时发生了两件事,
内存发生变化,vptr被写入,程序到达内存申请函数外
跳出。
又到达子类构造函数外,此时参数string构造完成
再跟进
String类构造函数
跳出
跟进,内存发生变化,m_pObj_Name值发生变化
到达基类构造函数体。
我们发现是在vptr值改变完成后再对初始化列表内的值进行初始化的。
跳出
到达子类构造函数体外。
再跟进
内存发生变化
再跟进
程序到达string参数构造函数
再跟进
再进一步,程序到达子类函数体
到此我们得到如下构造顺序
1.构造子类构造函数的参数
2.子类调用基类构造函数
3.基类设置vptr
4.基类初始化列表内容进行构造
5. 基类函数体调用
6. 子类设置vptr
7. 子类初始化列表内容进行构造
8. 子类构造函数体调用
(注意一点,初始化列表内的数据不按书写顺序,而是按类内部的定义顺序)
再看看gcc对于带于虚函数的单继承类的构造过程,我试用了eclipse,code blocks,qt-creater分别进行调式,他们对于内存查看的支持都不正常,有的甚至难以使用。看来M$的工具的易用性还是其它所不能比的,最终我只能在命令行手动了。下面是一份log
GNU gdb (GDB) 7.1
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "mingw32".
For bug reporting instructions, please see:
Reading symbols from C:\Users\saturnman\cpp/construct.exe...done.
(gdb) start
Temporary breakpoint 1 at 0x4014cd: file construct.cpp, line 88.
Starting program: C:\Users\saturnman\cpp/construct.exe
[New Thread 6716.0x2500]
Temporary breakpoint 1, main () at construct.cpp:88
88 Derived* d1 = new(buff1) Derived("Derived_1");
(gdb) set logging on
Copying output to gdb.txt.
(gdb) p (void*) buff1
$1 = (void *) 0x28faf0
(gdb) x /20x 0x28faf0
0x28faf0: 0x008a0150 0x008a9418 0x00000001 0x00000011
0x28fb00: 0x00000004 0x00000000 0x008a9380 0x00000000
0x28fb10: 0x00344588 0x7790a47f 0x00344598 0x00000a48
0x28fb20: 0xfeeefeee 0x00000005 0x00344560 0x00340000
0x28fb30: 0x00344fe0 0x7794fd4e 0x7b04057a 0x00000011
(gdb) step
operator new (__p=0x28faf0)
at c:/mingw32/bin/../lib/gcc/mingw32/4.5.0/include/c++/new:103
103 inline void* operator new(std::size_t, void* __p) throw() { return __p;
}
(gdb)
Derived (this=0x28faf0, name=...) at construct.cpp:63
63 Base(name)
(gdb) x /20x 0x28faf0
0x28faf0: 0x008a0150 0x008a9418 0x00000001 0x00000011
0x28fb00: 0x00000004 0x00000000 0x008a9380 0x00000000
0x28fb10: 0x00344588 0x7790a47f 0x00344598 0x00000a48
0x28fb20: 0xfeeefeee 0x00000005 0x00344560 0x00340000
0x28fb30: 0x00344fe0 0x7794fd4e 0x7b04057a 0x00000011
(gdb) step
Base (this=0x28faf0, name=...) at construct.cpp:31
31 m_pObj_Name(new string(name))
(gdb) x /20x 0x28faf0
0x28faf0: 0x008a0150 0x008a9418 0x00000001 0x00000011
0x28fb00: 0x00000004 0x00000000 0x008a9380 0x00000000
0x28fb10: 0x00344588 0x7790a47f 0x00344598 0x00000a48
0x28fb20: 0xfeeefeee 0x00000005 0x00344560 0x00340000
0x28fb30: 0x00344fe0 0x7794fd4e 0x7b04057a 0x00000011
(gdb) step
33 cout<<"In Base::constructor."<
(gdb) x /20x 0x28faf0
0x28faf0: 0x00473638 0x12345678 0x003445c0 0x00000011
0x28fb00: 0x00000004 0x00000000 0x008a9380 0x00000000
0x28fb10: 0x00344588 0x7790a47f 0x00344598 0x00000a48
0x28fb20: 0xfeeefeee 0x00000005 0x00344560 0x00340000
0x28fb30: 0x00344fe0 0x7794fd4e 0x7b04057a 0x00000011
(gdb) step
In Base::constructor.
34 cout<<"Constructing "<<*m_pObj_Name<
(gdb)
Constructing Derived_1
35 cout<<"binary info:"<
(gdb)
binary info:
36 show_byte((pointer_type)this,sizeof(*this));
(gdb)
show_byte (pointer=0x28faf0 "86G", length=12) at construct.cpp:11
11 cout<<"address of object:"<
(gdb) finish
Run till exit from #0 show_byte (pointer=0x28faf0 "86G", length=12)
at construct.cpp:11
address of object:28faf0
var size in byte:12
|address: 0x28faf0 | value:0x38 |
|address: 0x28faf1 | value:0x36 |
|address: 0x28faf2 | value:0x47 |
|address: 0x28faf3 | value:0x0 |
|address: 0x28faf4 | value:0x78 |
|address: 0x28faf5 | value:0x56 |
|address: 0x28faf6 | value:0x34 |
|address: 0x28faf7 | value:0x12 |
|address: 0x28faf8 | value:0xc0 |
|address: 0x28faf9 | value:0x45 |
|address: 0x28fafa | value:0x34 |
|address: 0x28fafb | value:0x0 |
0x0041fe4e in Base (this=0x28faf0, name=...) at construct.cpp:36
36 show_byte((pointer_type)this,sizeof(*this));
(gdb) x /20x 0x28faf0
0x28faf0: 0x00473638 0x12345678 0x003445c0 0x00000011
0x28fb00: 0x00000004 0x00000000 0x008a9380 0x00000000
0x28fb10: 0x00344588 0x7790a47f 0x00344598 0x00000a48
0x28fb20: 0xfeeefeee 0x00000005 0x00344560 0x00340000
0x28fb30: 0x00344fe0 0x7794fd4e 0x7b04057a 0x00000011
(gdb) step
_Unwind_SjLj_Unregister (fc=0x28f59c)
at ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c:208
208 ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c: No such file or direc
tory.
in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c
(gdb)
_Unwind_SjLj_SetContext (fc=0x28f59c)
at ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c:195
195 in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c
(gdb)
198 in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c
(gdb)
199 in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c
(gdb)
__gthread_setspecific (fc=0x28f59c)
at ../../../../gcc-4.5.0/libgcc/../gcc/gthr-win32.h:621
621 ../../../../gcc-4.5.0/libgcc/../gcc/gthr-win32.h: No such file or direct
ory.
in ../../../../gcc-4.5.0/libgcc/../gcc/gthr-win32.h
(gdb)
_Unwind_SjLj_Unregister (fc=0x28f59c)
at ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c:209
209 ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c: No such file or direc
tory.
in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c
(gdb)
Base (this=0x28faf0, name=...) at construct.cpp:37
37 }
(gdb)
Derived (this=0x28faf0, name=...) at construct.cpp:65
65 cout<<"In Derived::constructor."<
(gdb) x /20x 0x28faf0
0x28faf0: 0x00473658 0x12345678 0x003445c0 0x003445ec
0x28fb00: 0x00000004 0x00000000 0x008a9380 0x00000000
0x28fb10: 0x00344588 0x7790a47f 0x00344598 0x00000a48
0x28fb20: 0xfeeefeee 0x00000005 0x00344560 0x00340000
0x28fb30: 0x00344fe0 0x7794fd4e 0x7b04057a 0x00000011
(gdb) step
In Derived::constructor.
66 cout<<"binary info:"<
(gdb) x /20x 0x28faf0
0x28faf0: 0x00473658 0x12345678 0x003445c0 0x003445ec
0x28fb00: 0x00000004 0x00000000 0x008a9380 0x00000000
0x28fb10: 0x00344588 0x7790a47f 0x00344598 0x00000a48
0x28fb20: 0xfeeefeee 0x00000005 0x00344560 0x00340000
0x28fb30: 0x00344fe0 0x7794fd4e 0x7b04057a 0x00000011
(gdb) q
A debugging session is active.
Inferior 1 [process 6716] will be killed.
Quit anyway? (y or n) y
能看懂的就看一下,我把关键部分加红了,其与vs2010的差别不大,但是其设置vptr却是在初使化列表完成之后,构造函数体内代码运行之前,可以认为gcc是把设置vptr的代码入在了构造函数体代码的最前端。