深度探索c++对象模型(4)
2017-09-26 15:49
453 查看
从最简单的问题开始
class X{}; class Y:virtual public X{}; class Z:virtual public X{}; class A:public Y, public Z{};
每个类的大小是多少呢?我在vs2015上面的得到的答案是这样的(这里我使用了一个小技巧,在vs中,在项目——属性——配置属性——C/C++——命令行——其他选项中添加选项“/d1reportAllClassLayout”。再次编译时候,编译器会输出所有定义类的对象模型。)
1> class X size(1): 1> class Y size(4): 1> 0 | {vbptr} 1> +--- 1> +--- (virtual base X) 1> class Z size(4): 1> +--- 1> 0 | {vbptr} 1> +--- 1> +--- (virtual base X) 1> class A size(8): 1> +--- 1> 0 | +--- (base class Y) 1> 0 | | {vbptr} 1> | +--- 1> 4 | +--- (base class Z) 1> 4 | | {vbptr} 1> | +--- 1> +--- 1> +--- (virtual base X)
我们可以看到这里的X的大小是1,这是被编译器安插进去的一个char。这使得class的两个不同的对象可以在内存中得到不同的地址。
我们再来看Y和Z。首先我们要明白的是实现虚继承,将要带来一些额外的负担——额外需要一个某种形式的指针。到目前为止,对于一个32位的机器来说Y、Z的大小应该为5,而不是8或者4。我们需要再考虑两点因素:内存对齐(alignment)和编译器的优化。
考虑内存对齐的原因,Y和Z的大小会增加到4的倍数,也就是相应的补齐3bytes。但是在现在大多数的编译器上做了这样的一个优化处理。因为既然我的类里面有了成员,那么就不需要为空的类按插一个char,那么这是类的大小就是4,所以也就不用3bytes去填补,所以最终Y和Z的大小就是4。关于内存对齐的问题,我不准备在本文展开。大家有兴趣可以参看我的另一篇文章关于内存对齐的总结
关于单继承问题
我们先考虑一个简单的问题,单一继承并且没有virtual functionclass X { int x; }; class Y :public X { int y; };
得到的结果是
1> class X size(4): 1> +--- 1> 0 | x 1> +--- 1> 1> class Y size(8): 1> +--- 1> 0 | +--- (base class X) 1> 0 | | x 1> | +--- 1> 4 | y 1> +---
不能看出子类中继承了父类的成员,现在我们在此基础上增加一个虚函数看看
class X { int x; public: virtual void funX() {} }; class Y :public X { int y; public: virtual void funY(){} };
结果为
1> class X size(8): 1> +--- 1> 0 | {vfptr} 1> 4 | x 1> +--- 1> 1> X::$vftable@: 1> | &X_meta 1> | 0 1> 0 | &X::funX 1> 1> X::funX this adjustor: 0 1> 1> class Y size(12): 1> +--- 1> 0 | +--- (base class X) 1> 0 | | {vfptr} 1> 4 | | x 1> | +--- 1> 8 | y 1> +--- 1> 1> Y::$vftable@: 1> | &Y_meta 1> | 0 1> 0 | &X::funX 1> 1 | &Y::funY 1> 1> Y::funY this adjustor: 0
注意这个结果里面多出来了一个东西vftable很明显这个根据字面意思可以知道,这是虚函数指针对应的虚函数表。这里还有另一个东西adjustor,这是什么?
adjustor表示虚函数机制执行时,this指针的调整量,假如fun被多态调用的话,那么它的形式如下:
*(this+0)[0]()
总结虚函数调用形式,应该是:
*(this指针+调整量)[虚函数在vftable内的偏移]()
还有最后这个结果中要说的最后一点是,关于vfptr的位置问题,如你所见,在vs2015中它是被编译器放在了类的最前面,但是不是所有的编译器都是这样的。ok,开始下一个话题。
多重继承问题
依然从最简单的开始,不考虑virtual functionclass X { int x; }; class Y :public X { int y; };
class Z :public X
{
int z;
};
class A :public Y, public Z
{
int a;
};
结果是
1> class X size(4): 1> +--- 1> 0 | x 1> +--- 1> 1> class Y size(8): 1> +--- 1> 0 | +--- (base class X) 1> 0 | | x 1> | +--- 1> 4 | y 1> +---
1>
1> class Z size(8):
1> +---
1> 0 | +--- (base class X)
1> 0 | | x
1> | +---
1> 4 | z
1> +---
1>
1> class A size(20):
1> +---
1> 0 | +--- (base class Y)
1> 0 | | +--- (base class X)
1> 0 | | | x
1> | | +---
1> 4 | | y
1> | +---
1> 8 | +--- (base class Z)
1> 8 | | +--- (base class X)
1> 8 | | | x
1> | | +---
1> 12 | | z
1> | +---
1> 16 | a
1> +---
我们这里注意到A的大小是20,结合结构图不难理解,好,现在增加virtual function
class X { int x; public: virtual void funX() {} }; class Y :public X { int y; public: virtual void funY(){} };
class Z :public X
{
int z;
public:
virtual void funZ() {}
};
class A :public Y, public Z
{
int a;
public:
virtual void funA() {}
virtual void funY() {}
virtual void funZ() {}
};
结果是
1> class X size(8): 1> +--- 1> 0 | {vfptr} 1> 4 | x 1> +--- 1> 1> X::$vftable@: 1> | &X_meta 1> | 0 1> 0 | &X::funX 1> 1> X::funX this adjustor: 0 1> 1> class Y size(12): 1> +--- 1> 0 | +--- (base class X) 1> 0 | | {vfptr} 1> 4 | | x 1> | +--- 1> 8 | y 1> +--- 1> 1> Y::$vftable@: 1> | &Y_meta 1> | 0 1> 0 | &X::funX 1> 1 | &Y::funY 1> 1> Y::funY this adjustor: 0
1>
1> class Z size(12):
1> +---
1> 0 | +--- (base class X)
1> 0 | | {vfptr}
1> 4 | | x
1> | +---
1> 8 | z
1> +---
1>
1> Z::$vftable@:
1> | &Z_meta
1> | 0
1> 0 | &X::funX
1> 1 | &Z::funZ
1>
1> Z::funZ this adjustor: 0
1>
1> class A size(28):
1> +---
1> 0 | +--- (base class Y)
1> 0 | | +--- (base class X)
1> 0 | | | {vfptr}
1> 4 | | | x
1> | | +---
1> 8 | | y
1> | +---
1> 12 | +--- (base class Z)
1> 12 | | +--- (base class X)
1> 12 | | | {vfptr}
1> 16 | | | x
1> | | +---
1> 20 | | z
1> | +---
1> 24 | a
1> +---
1>
1> A::$vftable@Y@:
1> | &A_meta
1> | 0
1> 0 | &X::funX
1> 1 | &A::funY
1> 2 | &A::funA
1>
1> A::$vftable@Z@:
1> | -12
1> 0 | &X::funX
1> 1 | &A::funZ
1>
1> A::funA this adjustor: 0
1> A::funY this adjustor: 0
1> A::funZ this adjustor: 12
这里出现一个有意思的问题,在多重继承下,子类有两个虚函数表,分别来自两个父类,其中一个虚函数表中包含了子类的虚函数,而另一个没有。如果子类重写了任意父类的虚函数,都会覆盖对应的函数地址记录。
在这里我们发现A::funZ的函数对应的adjustor值是12,按照我们前边的规则,可以发现该函数的多态调用形式为:
*(this+12)[1]()
此处的调整量12正好是Z的vfptr在A对象内的偏移量。
这里我们注意到在子类A中有两个X,分别是Y中的X和Z中的X,这种做法我们认为是对内存的一种浪费,怎么解决这个问题呢?就引出了虚拟继承。
虚拟继承
class X { int x; public: virtual void funX() {} }; class Y :virtual public X { int y; public: virtual void funY(){} }; class Z :virtual public X { int z; public: virtual void funZ() {} }; class A :public Y, public Z { int a; public: virtual void funA() {} virtual void funY() {} virtual void funZ() {} };
结果是
1> class X size(8): 1> +--- 1> 0 | {vfptr} 1> 4 | x 1> +--- 1> 1> X::$vftable@: 1> | &X_meta 1> | 0 1> 0 | &X::funX 1> 1> X::funX this adjustor: 0 1> 1> class Y size(20): 1> +--- 1> 0 | {vfptr} 1> 4 | {vbptr} 1> 8 | y 1> +--- 1> +--- (virtual base X) 1> 12 | {vfptr} 1> 16 | x 1> +--- 1> 1> Y::$vftable@Y@: 1> | &Y_meta 1> | 0 1> 0 | &Y::funY 1> 1> Y::$vbtable@: 1> 0 | -4 1> 1 | 8 (Yd(Y+4)X) 1> 1> Y::$vftable@X@: 1> | -12 1> 0 | &X::funX 1> 1> Y::funY this adjustor: 0 1> vbi: class offset o.vbptr o.vbte fVtorDisp 1> X 12 4 4 0 1> 1> class Z size(20): 1> +--- 1> 0 | {vfptr} 1> 4 | {vbptr} 1> 8 | z 1> +--- 1> +--- (virtual base X) 1> 12 | {vfptr} 1> 16 | x 1> +--- 1> 1> Z::$vftable@Z@: 1> | &Z_meta 1> | 0 1> 0 | &Z::funZ 1> 1> Z::$vbtable@: 1> 0 | -4 1> 1 | 8 (Zd(Z+4)X) 1> 1> Z::$vftable@X@: 1> | -12 1> 0 | &X::funX 1> 1> Z::funZ this adjustor: 0 1> vbi: class offset o.vbptr o.vbte fVtorDisp 1> X 12 4 4 0 1> 1> class A size(36): 1> +--- 1> 0 | +--- (base class Y) 1> 0 | | {vfptr} 1> 4 | | {vbptr} 1> 8 | | y 1> | +--- 1> 12 | +--- (base class Z) 1> 12 | | {vfptr} 1> 16 | | {vbptr} 1> 20 | | z 1> | +--- 1> 24 | a 1> +--- 1> +--- (virtual base X) 1> 28 | {vfptr} 1> 32 | x 1> +--- 1> 1> A::$vftable@Y@: 1> | &A_meta 1> | 0 1> 0 | &A::funY 1> 1 | &A::funA 1> 1> A::$vftable@Z@: 1> | -12 1> 0 | &A::funZ 1> 1> A::$vbtable@Y@: 1> 0 | -4 1> 1 | 24 (Ad(Y+4)X) 1> 1> A::$vbtable@Z@: 1> 0 | -4 1> 1 | 12 (Ad(Z+4)X) 1> 1> A::$vftable@X@: 1> | -28 1> 0 | &X::funX 1> 1> A::funA this adjustor: 0 1> A::funY this adjustor: 0 1> A::funZ this adjustor: 12 1> vbi: class offset o.vbptr o.vbte fVtorDisp 1> X 28 4 4 0
结果比较复杂,用一张图概括就是
我们可以看到。这里的class A内部只有一个class X了,这张图有个小的错误,就是y和z的
vbtable后面少画了一个0。这个0是作为一种标记,标记这个表的结束位置。
vbtable表中的-4指的是y和z的
vbtable相对于自身的偏移。24表示的是y的
vbtable距离x的
vfptr的偏移,同样的12表示的是z的
vbtable距离x的
vfptr的偏移。至此所有问题全部解决了?
等一下,还有一个问题呢!!!编译器生成的表中有的虚函数表的前面都会有这样
_meta结尾,这个是什么呢?深度探索c++对象模型(7)
相关文章推荐
- 深度探索C++对象模型之局部静态对象
- 读【深度探索C++对象模型】【下】
- 【深度探索C++对象模型】第一章 关于对象
- 深度探索C++对象模型(4)
- 读书笔记-深度探索C++对象模型-Chapter7
- 深度探索C++对象模型之前言
- 《深度探索c++ 对象模型》有感之构造函数和析构函数不能调用虚函数
- Inside the C++ Object Model 深度探索对象模型 1-Object, 2-Constructor
- 深度探索C++ 对象模型【第二章1】
- 读【深度探索C++对象模型】【下】
- 深度探索C++ 对象模型【第五章2】
- 深度探索C++对象模型:4.Function语意学
- 深度探索C++对象模型--------默认构造函数
- 深度探索C++对象模型:4.Function语意学
- 深度探索c++对象模型——读书笔记(一)
- 深度探索c++对象模型
- 深度探索C++对象模型之:成员函数语义学--静态成员函数
- 深度探索c++对象模型 学习笔记 chapter2-1 default constructor
- 深度探索C++对象模型复习和学习 第三章 Data 语义学(The Semantics of Data )
- 深度探索C++对象模型学习笔记——Function语意学