前一段时间再次拜读《Inside the C++ Object Model》 深入探索C++对象模型,有了进一步的理解,因此我也写了四篇博文算是读书笔记:
Program Transformation Semantics (程序转换语义学)
The Semantics of Copy Constructors(拷贝构造函数之编译背后的行为)
The Semantics of Constructors: The Default Constructor (默认构造函数什么时候会被创建出来)
The Semantics of Data: Data语义学 深入探索C++对象模型
这些文章都获得了很大的浏览量,虽然类似的博文原来都有,可能不容易被现在仍活跃在CSDN Blog的各位同仁看到吧。因此萌生了接着将这这本书读完的同时,再接着谈一下我的理解,或者说读书笔记。
关于C++虚函数,很多博文从各个角度来探究虚函数是如何实现的,或者说编译器是如何实现虚函数的。比较经典的文章有陈皓先生的《C++虚函数表解析》和《C++对象内存布局》。本文通过GDB来从另外一个角度来理解C++ object的内存布局,一来熟悉语言背后编译器为了实现语言特性为我们做了什么;二来熟悉使用GDB来调试程序。
同时,本文也将对如何更好的理解C++语言提供了一个方法:使用GDB,可以很直观的理解编译器的实现,从根本上掌握C++!我们不单单只会开车,还应该知道车的内部的构造。
2、带有虚函数的单一继承
-
class Parent
-
{
-
public:
-
Parent():numInParent(
1111)
-
{}
-
virtual void Foo(){
-
};
-
virtual void Boo(){
-
};
-
private:
-
int numInParent;
-
};
-
-
class Child:
public Parent
-
{
-
public:
-
Child():numInChild(
2222){}
-
virtual void Foo(){
-
}
-
int numInChild;
-
};
-
编译时不要忘记-g,使得gdb可以把各个地址映射成函数名。
-
(gdb)
set p obj on
-
(gdb) p *
this
-
$2 = (Child) {<Parent> = {_vptr.Parent = <span class="hljs-number">0x400a30</span>, numInParent = <span class="hljs-number">1111</span>}, numInChild = <span class="hljs-number">2222</span>}</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">(gdb) <span class="hljs-built_in">set</span> p pretty on</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">(gdb) p *<span class="hljs-keyword">this</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">$
3 = (Child) {
-
<Parent> = {
-
_vptr.Parent =
0x400a30,
-
numInParent =
1111
-
},
-
members of Child:
-
numInChild =
2222
-
}
-
(gdb) p /a (*(
void ***)
this)[
0]@
3
-
$
4 = {
0x4008ec <Child::Foo()>,
0x4008b4 <Parent::Boo()>,
0x6010b0 <_ZTVN10__cxxabiv120__si_class_type_infoE@@CXXABI_1
.3+
16>}
解释一下gdb的命令:
set p obj <on/off>: 在C++中,如果一个对象指针指向其派生类,如果打开这个选项,GDB会自动按照虚方法调用的规则显示输出,如果关闭这个选项的话,GDB就不管虚函数表了。这个选项默认是off。 使用show print object查看对象选项的设置。
set p pertty <on/off>: 按照层次打印结构体。可以从设置前后看到这个区别。on的确更容易阅读。
p /a (*(void ***)this)[0]@3
就是打印虚函数表了。因为知道是两个,可以仅仅打印2个元素。为了知道下一个存储了什么信息,我们打印了3个值。实际上后几个元素存储了Parent 和Child的typeinfo name和typeinfo。
总结:
对于单一继承,
1. vptr存储到了object的开始。
2. 在vptr之后,从Parent开始的data member按照声明顺序依次存储。
3. 多重继承,包含有相同的父类
对应的C++codes:
-
class Point2d{
-
public:
-
virtual void Foo(){}
-
virtual void Boo(){}
-
virtual void non_overwrite(){}
-
protected:
-
float _x, _y;
-
};
-
-
class Vertex:
public Point2d{
-
public:
-
virtual void Foo(){}
-
virtual void BooVer(){}
-
protected:
-
Vertex *next;
-
};
-
-
class Point3d:
public Point2d{
-
public:
-
virtual void Boo3d(){}
-
protected:
-
float _z;
-
};
-
-
class Vertex3d:
public Vertex,
public Point3d{
-
public:
-
void test(){}
-
protected:
-
float mumble;
-
};
使用GDB打印的对象内存布局:
-
<Vertex> = {
-
<Point2d> = {
-
_vptr.Point2d = 0x400ab0,
-
_x =
5.88090213e-39,
-
_y =
0
-
},
-
members of Vertex:
-
next =
0x0
-
},
-
<Point3d> = {
-
<Point2d> = {
-
_vptr.Point2d = 0x400ae0,
-
_x = -nan(
0x7fe180),
-
_y =
4.59163468e-41
-
},
-
members of Point3d:
-
_z =
0
-
},
-
members of Vertex3d:
-
mumble =
0
-
}
可见v3d有两个vptr,指向不同的vtable。首先看一下第一个:
-
(gdb) p /a (*(
void ***)
this)[
0]@
5
-
$<span class="hljs-number">9</span> = {<span class="hljs-number">0x4008be</span> <Vertex::Foo()>,</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">0x4008aa</span> <Point2d::Boo()>,</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">0x4008b4</span> <Point2d::non_overwrite()>,</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">0x4008c8</span> <Vertex::BooVer()>,</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-number">0xffffffffffffffe8</span>}</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">(gdb) p /a (*(<span class="hljs-keyword">void</span> ***)<span class="hljs-keyword">this</span>)[<span class="hljs-number">0</span>]@<span class="hljs-number">6</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">$
10 = {
0x4008be <Vertex::Foo()>,
-
0x4008aa <Point2d::Boo()>,
-
0x4008b4 <Point2d::non_overwrite()>,
-
0x4008c8 <Vertex::BooVer()>,
-
0xffffffffffffffe8,
-
0x400b00 <_ZTI8Vertex3d>}
-
(gdb) info addr _ZTI8Vertex3d
-
Symbol
"typeinfo for Vertex3d" is at
0x400b00 in a file compiled without debugging.
你可以注意到了,vtable打印分行了,可以使用 set p array on将打印的数组分行,以逗号结尾。
注意到该虚函数表以
0xffffffffffffffe8
结尾。在单一继承中是没有这个结束标识的。
接着看第二个vtable:
-
(gdb) p /a (*(
void ***)
this)[
1]@
5
-
$
11 = {
0x4008b2 <Point2d::Boo()>,
-
0x4008bc <Point2d::non_overwrite()>,
-
0x4008d0 <Vertex::BooVer()>,
-
0xffffffffffffffe8,
-
0x400b00 <_ZTI8Vertex3d>}
-
(gdb) info addr _ZTI8Vertex3d
-
Symbol
"typeinfo for Vertex3d" is at
0x400b00 in a file compiled without debugging.
当然这个只是为了举个例子。现实中很少有人这么干吧。比如访问Foo,下面的code将会导致歧义性错误:
v3d.Boo();
error: request for member Boo is ambiguous
multiInheritance.cpp:8: error: candidates are: virtual void Point2d::Boo()
只能指定具体的subobject才能进行具体调用:
v3d.::Vertex::Boo();
4. 虚拟继承
C++ codes:
-
class Point2d{
-
public:
-
virtual void Foo(){}
-
virtual void Boo(){}
-
virtual void non_overwrite(){}
-
protected:
-
float _x, _y;
-
};
-
-
class Vertex:
public
virtual Point2d{
-
public:
-
virtual void Foo(){}
-
virtual void BooVer(){}
-
protected:
-
Vertex *next;
-
};
-
-
class Point3d:
public
virtual Point2d{
-
public:
-
virtual void Boo3d(){}
-
protected:
-
float _z;
-
};
-
-
class Vertex3d:
public Vertex,
public Point3d{
-
public:
-
void test(){}
-
protected:
-
float mumble;
-
};
继承关系图:
使用gdb打印object的内存布局:
-
(gdb) p *
this
-
$
10 = (Vertex3d) {
-
<Vertex> = {
-
<Point2d> = {
-
_vptr.Point2d = 0x400b70,
-
_x =
0,
-
_y =
0
-
},
-
members of Vertex:
-
_vptr.Vertex = 0x400b18,
-
next =
0x4009c0
-
},
-
<Point3d> = {
-
members of Point3d:
-
_vptr.Point3d = 0x400b40,
-
_z =
5.87993804e-39
-
},
-
members of Vertex3d:
-
mumble =
0
-
}
gdb打印的vptr相关:
-
(gdb) p /a (*(
void ***)
this)[
0]@
60
-
$
25 = {
0x400870 <Vertex::Foo()>,
-
0x40087a <Vertex::BooVer()>,
-
0x10,
-
0xfffffffffffffff0,
-
0x400c80 <_ZTI8Vertex3d>, #
"typeinfo for Vertex3d"
-
0x400884 <Point3d::Boo3d()>,
-
0x0,
-
0x0,
-
0xffffffffffffffe0,
-
0xffffffffffffffe0,
-
0x400c80 <_ZTI8Vertex3d>, #
"typeinfo for Vertex3d"
-
0x400866 <_ZTv0_n24_N6Vertex3FooEv>, #
"virtual thunk to Vertex::Foo()"
-
0x400852 <Point2d::Boo()>,
-
0x40085c <Point2d::non_overwrite()>,
-
0x0,
-
0x0,
-
0x0,
-
0x20,
-
0x0,
-
0x400cc0 <_ZTI6Vertex>, #
"typeinfo for Vertex"
-
0x400870 <Vertex::Foo()>,
-
0x40087a <Vertex::BooVer()>,
-
0x0,
-
0x0,
-
0xffffffffffffffe0,
-
0xffffffffffffffe0,
-
0x400cc0 <_ZTI6Vertex>, #
"typeinfo for Vertex"
-
0x400866 <_ZTv0_n24_N6Vertex3FooEv>, #
"virtual thunk to Vertex::Foo()"
-
0x400852 <Point2d::Boo()>,
-
0x40085c <Point2d::non_overwrite()>,
-
0x0,
-
0x0,
-
0x0,
-
0x10,
-
0x0,
-
0x400d00 <_ZTI7Point3d>, #
"typeinfo for Point3d"
-
0x400884 <Point3d::Boo3d()>,
-
0x0,
-
0x0,
-
0x0,
-
0xfffffffffffffff0,
-
0x400d00 <_ZTI7Point3d>, #
"typeinfo for Point3d"
-
0x400848 <Point2d::Foo()>,
-
0x400852 <Point2d::Boo()>,
-
0x40085c <Point2d::non_overwrite()>,
-
0x6020b0 <_ZTVN10__cxxabiv121__vmi_class_type_infoE@@CXXABI_1
.3+
16>,
-
0x400d28 <_ZTS8Vertex3d>,
-
0x200000002,
-
0x400cc0 <_ZTI6Vertex>, #
"typeinfo for Vertex"
-
0x2,
-
0x400d00 <_ZTI7Point3d>, #
"typeinfo for Point3d"
-
0x1002,
-
0x0,
-
0x6020b0 <_ZTVN10__cxxabiv121__vmi_class_type_infoE@@CXXABI_1
.3+
16>,
-
0x400d32 <_ZTS6Vertex>,
-
0x100000000,
-
0x400d40 <_ZTI7Point2d>,
-
0xffffffffffffe803,
-
0x0,
-
0x0}
有兴趣的话可以看一下反汇编的vtable的构成。
参考:
1. http://stackoverflow.com/questions/6191678/print-c-vtables-using-gdb
2. http://stackoverflow.com/questions/18363899/how-to-display-a-vtable-by-name-using-gdb
尊重原创,转载请注明出处: anzhsoft http://blog.csdn.net/anzhsoft/article/details/18600163