C++虚继承的内存结构

我用vc2003观测到的实际情况是。在类中增加一个指针(VBPTR)指向一个VBTBL,这个VBTBL的第一项记载的是从VBPTR 与本类的偏移地址,如果本类有虚函数,那么第一项是FF FF FF FC(也就是-4),如果没有则是零,第二项起是VBPTR与本类的虚基类的偏移值。vc2003的这种方案个人觉得没有Bjarne的好,一是要多一个指针,二是因为VBPTR与虚函数表分开设计,也不便于修改。至于其它编译器,因为我跟其它编译器不熟,所以也就没有实测它们。

下面给出对于类定义

struct B1

{

    int a;

    int b;

};

struct B2

{

    virtual void foo(void);

    int c;

    int d;

};

 

struct Test : virtual public B1, virtual public B2

{

    virtual void func1(void);

    virtual void func2(void);

    virtual void func3(void);

    int X;

};

一个Test 对象的内存布局图,我们可以清楚的看到在VS2003中VBPTR以及VBTBL的结构以及其相关的内容是什么意义。以及Bjarne的方案的优点。

 vs2003下的VBPTR和VBTBL

 

最后我们来看一个完整的例子以及内存结构布局。图后有相关代码。

VS2003虚继承内存结构图

 

 

代码如下:

struct A

{

    A(int v=100):X(v){};

    virtual void foo(void){}

    int X;

};

 

struct B :virtualpublic A

{

    B(int v=10):Y(v),A(100){};

    virtual void fooB(void){}

    int Y;

};

 

struct C : virtual public A

{

    C(int v=20):Z(v),A(100){}

    virtual void fooC(void){}

    int Z;

};

 

 

struct D : public B, public C

{

    D(int v =40):B(10),C(20),A(100),L(v){}

    virtual void fooD(void){}

    int L;

};

 

 

int _tmain(int argc, _TCHAR* argv[])

{

   

    A a;

    int *ptr;

    ptr = (int*)&a;

    cout << ptr << " sizeof = " << sizeof(a) <<endl;

    for(int i=0;i<sizeof(A)/sizeof(int);i++)

    {

        if(ptr[i] < 10000)

        {

             cout << dec << ptr[i]<<endl;

        }

        else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;

    }

 

    cout << "--------------------------------------" <<endl;

 

    B b;

    ptr = (int*)&b;

    cout <<"addr:" << ptr << " sizeof = " << sizeof(b) <<endl;

    for(int i=0;i<sizeof(B)/sizeof(int);i++)

    {

        if(ptr[i] < 10000)

        {

             cout << dec << ptr[i]<<endl;

        }

        else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;

    }

 

    cout << "--------------------------------------" <<endl;

   

    D d;

    ptr = (int*)&d;

    cout <<"addr:" << ptr << " sizeof = " << sizeof(d) <<endl;

    for(int i=0;i<sizeof(D)/sizeof(int);i++)

    {

        if(ptr[i] < 10000)

        {

             cout << dec << ptr[i]<<endl;

        }

        else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;

    }

    return 0;

}

 

 

 

另外看的一篇文章也很有启发性,大致内存结构也与上同

最近在研究虚继承,对带有虚函数的虚拟继承的内存布局有些疑惑,求大神指点,最好带图的,谢谢!

例如下面的代码:


        
        
  1. class A            
  2. { 
  3.     public: 
  4.         A(){cout << "A called"<< endl;} 
  5.  
  6.         virtual void print(){cout << "A print" <<endl;} 
  7.     
  8. }; 
  9.   
  10. class B :public virtual A 
  11. { 
  12.     public: 
  13.         B(){cout << "B called" << endl;} 
  14.  
  15.         void print(){cout << "B print" << endl;}          
  16. };
 

    单个的虚继承没有意义,而且它的布局和普通的单继承完全一样。
    虚继承主要用于多继承中的菱形继承
    ,举例如下:

           
           
    1. class Top 
    2. { 
    3.     public: 
    4.         int a; 
    5. }; 
    6.  
    7. class Left : virtual public Top 
    8. { 
    9.     public: 
    10.         int b; 
    11. }; 
    12.  
    13. class Right : virtual public Top 
    14. { 
    15.     public: 
    16.         int c; 
    17. }; 
    18.  
    19. class Bottom : public Left, public Right 
    20. { 
    21.     public: 
    22.         int d; 
    23. };

    它的继承关系图如下:

    请输入图片描述
    可以得出 Bottom 的内存结构可能是这样子的。

    请输入图片描述
    从图中可以看出,Top 中的数据只保存了一份,也就是说,虚拟继承其实就是共享数据的意思。
    但是虚继承的内存结构实际上没有这么简单,它应该是这样子的:

    请输入图片描述
    上图有两点值得大家注意。第一点就是类中成员分布顺序是完全不一样的(实际上可以说是正好相反)。第二点,类中增加了vptr指针,这些是被编译器在编译过程中插入到类中的(在设计类时如果使用了虚继承,虚函数都会产生相关vptr)。同时,在类的构造函数中会对相关指针做初始化,这些也是编译器完成的工作。Vptr指针指向了一个“virtual table”。在类中每个虚基类都会存在与之对应的一个vptr指针。

     

    我对虚继承、普通继承中虚函数的内存放置总结成几句话:

    1、虚继承时子与基需分开。子在前,基在后。(子类的虚函数与积累的虚函数需要分开,不可覆盖)

    2、普通继承时,基在前,子在后。如果是多重继承,则顺序是基1-基2-最子类-最基类(子类的虚函数如果和基类相同,会覆盖子类的虚函数)

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

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

    余额充值