您的位置:首页 > 编程语言 > C语言/C++

深度探索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 function

class 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 function

class 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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: