C++中,类继承的内存布局

对于普通的继承,不涉及虚拟继承时,比较简单。

如果继承多个父类则:

每个父类都有自己的虚表,子类虚成员函数加到第一个父类的虚函数列表后面

其它数据成员,按顺序依次放在各个父类的虚函数列表后面

以下内容,摘自网友

[0] Base1::_vptr->

     [0] Derive::f()

     [1] Base1::g()

     [2] Base1::h()

     [3] Driver::g1()

     [4] 00000000      ç 注意:在GCC下,这里是1

[1] Base1.ibase1 = 10

[2] Base2::_vptr->

     [0] Derive::f()

     [1] Base2::g()

     [2] Base2::h()

     [3] 00000000      ç 注意:在GCC下,这里是1

[3] Base2.ibase2 = 20

[4] Base3::_vptr->

     [0] Derive::f()

     [1] Base3::g()

     [2] Base3::h()

     [3] 00000000

[5] Base3.ibase3 = 30

[6] Derive.iderive = 100


使用图片表示是下面这个样子:

 

 

我们可以看到:

1)  每个父类都有自己的虚表。

2)  子类的成员函数被放到了第一个父类的表中。

3)  内存布局中,其父类布局依次按声明顺序排列。

4)  每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

 


———————————————————————————————————————————————————

对于有虚拟继承的情况

很高深的东西,搞不太明白,这时候似乎还有一个虚类指针


虚拟继承

即派生类继承多次基类,但在派生类中只存在一份基类的拷贝。编译器实现虚拟继承的方式并不相同,下面我结合VS2010来探讨C++虚拟继承。声明一个虚基类CommonBase,两个从虚基类虚拟派生Base1和Base2,然后D,公有多继承自Base1和Base2,具体类定义如下:

复制代码
class CommonBase
{
public:
    virtual void commonBaseFunc() = 0;
private:
    int commonBase_data;
};

class Base1 : public virtual CommonBase
{
public:
    virtual void Base1_Fun1(){}
    virtual void Base1_Fun2(){}
    virtual void commonBaseFunc(){}
private:
    int Base1_data;
};

class Base2 : public virtual CommonBase
{
public:
    virtual void Base2_Fun1(){}
    virtual void Base2_Fun2(){}
private:
    int Base2_data;
};

class Derived : public Base1, public Base2
{
public:
    virtual void Base1_Fun1(){}
    virtual void Base2_Fun2(){}
private:
    int Derived_data;
};
复制代码

现在我们来看看Derived类对象的内存布局,编译之后,我们在生成窗口看到如下信息:

复制代码
1>  class Derived    size(36):
1>      +---
1>      | +--- (base class Base1)
1>   0    | | {vfptr}
1>   4    | | {vbptr}
1>   8    | | Base1_data
1>      | +---
1>      | +--- (base class Base2)
1>  12    | | {vfptr}
1>  16    | | {vbptr}
1>  20    | | Base2_data
1>      | +---
1>  24    | Derived_data
1>      +---
1>      +--- (virtual base CommonBase)
1>  28    | {vfptr}
1>  32    | commonBase_data
1>      +---
1>  
1>  Derived::$vftable@Base1@:
1>      | &Derived_meta
1>      |  0
1>   0    | &Derived::Base1_Fun1
1>   1    | &Base1::Base1_Fun2
1>  
1>  Derived::$vftable@Base2@:
1>      | -12
1>   0    | &Base2::Base2_Fun1
1>   1    | &Derived::Base2_Fun2
1>  
1>  Derived::$vbtable@Base1@:
1>   0    | -4
1>   1    | 24 (Derivedd(Base1+4)CommonBase)
1>  
1>  Derived::$vbtable@Base2@:
1>   0    | -4
1>   1    | 12 (Derivedd(Base2+4)CommonBase)
1>  
1>  Derived::$vftable@CommonBase@:
1>      | -28
1>   0    | &thunk: this-=16; goto Base1::commonBaseFunc
1>  
1>  Derived::Base1_Fun1 this adjustor: 0
1>  Derived::Base2_Fun2 this adjustor: 12
1>  
1>  vbi:       class  offset o.vbptr  o.vbte fVtorDisp
1>        CommonBase      28       4       4 0
复制代码

    从输出的信息来看,虚拟继承有别于非虚拟继承的一个区别就是把虚基类放在整个类对象的末尾,而非虚拟继承则是按照继承的顺序先存放父类,再到子类。下面我们就来说说这些信息究竟表示什么含义。

    对于size(36)就指明Derived对象的大小为36个字节,我们看看Base2的vftable,其中-12表示Base2在Derived类中的偏移量。Base1的vbtable中-4表示Base1的vbtable与Base1这个对象的偏移量,因为一般都是把vftable放在对象的前面,所以vbtable与对象首地址的偏移量一般就是中间隔着vftable,24表示Base1的vbtable与虚基类CommonBase首地址的偏移量,从上图可以看出Base1的vbtable与Derived的偏移量为4,CommonBase与Derived的偏移量为28,这24加上4就刚好等于28,同理,Base2也一样。

    我们着重来看一下,CommonBase的vftable,-28就不用多说了,表示CommonBase相对于Derived首地址的偏移量,重点是这句&thunk:this-=16;goto Base1::commonBaseFunc,这句就说明VS2010使用了thunk技术来实现虚拟继承的时候,虚基类CommonBase只存在一份拷贝

在Derived,由于Base1已经重写了CommonBase的虚函数commonBaseFunc(),所以当Derived调用commonBaseFunc()函数时才会跳转到

Base1去执行。

    Derived::Base2_Fun2 this adjustor:12表示当一个Derived对象d调用Base2的Base2_Fun2()函数时,this指针需要跳转的偏移量,从图上可以看出12就是Base2相对于Derived首地址的偏移量。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zlingh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值