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

C++虚继承的内存结构

2013-11-04 20:50 225 查看
我用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的方案的优点。

 


 

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



 

 

代码如下:

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;
}

 

 

 

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

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

例如下面的代码:

  
class A            



    public: 

        A(){cout << "A called"<< endl;} 

 

        virtual void print(){cout << "A print" <<endl;} 

    

}; 

  

class B :public virtual A 



    public: 

        B(){cout << "B called" << endl;} 

 

        void print(){cout << "B print" << endl;}          

};

 

单个的虚继承没有意义,而且它的布局和普通的单继承完全一样。

虚继承主要用于多继承中的菱形继承
,举例如下:

  
class Top 



    public: 

        int a; 

}; 

 

class Left : virtual public Top 



    public: 

        int b; 

}; 

 

class Right : virtual public Top 



    public: 

        int c; 

}; 

 

class Bottom : public Left, public Right 



    public: 

        int d; 

};

它的继承关系图如下:



可以得出 Bottom 的内存结构可能是这样子的。



从图中可以看出,Top 中的数据只保存了一份,也就是说,虚拟继承其实就是共享数据的意思。

但是虚继承的内存结构实际上没有这么简单,它应该是这样子的:



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

 

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

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

2、普通继承时,基在前,子在后。如果是多重继承,则顺序是基1-基2-最子类-最基类(子类的虚函数如果和基类相同,会覆盖子类的虚函数)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: