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

多重虚继承的内存模型分析

2015-08-11 22:18 417 查看
这里就先从以下几个点进行说明吧:

 虚继承和虚基类
vs2010下的cl命令
内存模型

     虚继承和虚基类

     虚继承:在继承定义中包含了virtual关键字的继承关系;

     虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:class CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不不是虚继承体系中的基类。

     vs2010下的cl命令

     微软的VS2010提供了一个新的选项,给用户显示C++对象在内存中的布局。这个选项就是:

[cpp] view
plaincopyprint?

/d1reportSingleClassLayout  

具体使用方法如下,在写好相应的cpp文件之后,需要启动VS2010的命令行工具“Visual Studio 2010Command Prompt”,切换到cpp文件所在目录之后,输入如下的命令:

[cpp] view
plaincopyprint?

cl [filename].cpp /d1reportSingleClassLayout[className]  

cl当然就是MS的编译器;[filename].cpp就是你所想要查看的class所在的cpp文件(class定义在头文件也没关系,还是只要编译cpp文件即可);而你需要在最后加上[className],也就是你需要查看的class的类名。

【举例】test.cpp文件代码如下:

[cpp] view
plaincopyprint?

#include <iostream>  

using namespace std;  

  

class Base  

{  

public:  

    int a;  

    virtual void fcn() {};  

};  

  

class Derived : public Base  

{  

public:  

    virtual void fcn2() {};  

private:  

    int d;  

    void fcn3() { }  

};  

  

int main()   

{  

}  

查看Derived这个类的对象在内存中的布局,那么就可以用下面的命令行:

[cpp] view
plaincopyprint?

cl Test.cpp /d1reportSingleClassLayoutDerived  

结果显示如下:



       可以看到class Derived的对象的内存布局,在派生类对象的开始包含了基类Base的对象,其中有一个虚表指针,指向的就是下面的Derived::$vftable@ (virtual function table),表中包含了Derived类中所有的虚函数。

      内存模型

     在这一小节里面我主要从他面试的几个题目中来谈谈虚继承的内存模型。

     代码一: 

[cpp] view
plaincopyprint?

class A  

{  

    virtual void a()  

    {  

    }  

};  

class A1  

{  

    virtual void a()  

    {  

    }  

};  

  

class B : public A , virtual public A1  

{  

};  

void main()  

{  

    cout<<"sizeof A: "<<sizeof(A)<<endl;  

    cout<<"sizeof A1: "<<sizeof(A1)<<endl;  

    cout<<"sizeof B: "<<sizeof(B)<<endl;     //   

}  

输出结果是:



在命令行中输入:

[cpp] view
plaincopyprint?

cl test.cpp /d1reportSingleClassLayoutB  



         从这个内存布局就可以看出来class A、class A1和ClassB的大小,本身class A的大小应该是1bytes的内存定位大小加上虚函数指针4bytes因为有了虚函数指针后1bytes的占位就可以取消了。所以A的大小就是4bytes,同理Class A1。对于Class B它主要是从class A和class A1(虚继承)而来,所以B里面包含有一个A和A1同时因为是需继承所以就有一个指向虚基类(A1)的vbptr指针。这里为了方便我做个图直观一点:



        所以说class B的大小是12bytes

代码二:

[cpp] view
plaincopyprint?

class A  

{  

    int a;  

};  

class B  

{  

    int b;  

};  

  

class C  

{  

};  

class D  

{  

};  

class E: public virtual A , public virtual B , public virtual C , public virtual D  

{  

};  

void main()  

{  

    cout<<"sizeof E: "<<sizeof(E)<<endl;      //   

}  

       Class如果内含一个或多个virtual base class subobjects,将被分割为两部分:一个不变局部和一个共享局部.不变局部中的数据,不管后继如何衍化,总是拥有固定的offset(从object的开头算起),所以这一部分数据可以被直接存取。至于共享局部,所表现的就是virtual base class subobject。这一部分的数据,其位置会因为每次的派生操作而有变化,所以它们只可以被间接存取。各家编译器实现技术之间的差异就在于间接存取的方法不同。一般的布局策略是先安排好derived
class的不变部分,然后再建立其共享部分.然而,这中间存在着一个问题:如何能够存取Class的共享部分呢?我这就从微软的编译器来看它的内存布局。


        上面代码二输出的结果:



class E的大小是不是有点怪,在命令行中输入:

[cpp] view
plaincopyprint?

cl test.cpp /d1reportSingleClassLayoutE  



           如前面黑色字体标注一样,因为classE是多重虚继承,所以在内存中的布局是分为固定局部和共享局部,固定局部的大小就int a,b 所以是8 bytes。我的上一篇博文(VC++对象布局的奥秘:虚函数、多继承、虚拟继承)对这个内存某型有个大概的讲解,在这就不多言了。

[cpp] view
plaincopyprint?

E::$vbtable@:  

1>   0   | 0  

1>   1   | 4 (Ed(E+0)A)  

1>   2   | 8 (Ed(E+0)B)  

1>   3   | 12 (Ed(E+0)C)  

1>   4   | 16 (Ed(E+0)D)  

        一般都是把vbtable放在对象的前面,所以vbtable(virtual base class table)与对象首地址的偏移量一般就是中间隔着vbtable,这个地方的4表示E的vbtable与虚基类A首地址的偏移量,同理,8,12,16这个就不用我说了。既然这都给出了vbtable域虚基类的地址偏移量了,说明在E对象的内存中还是存在分配的空间。



          从vs2010的内存来看的确也是分配了,通过前面的vbtable的偏移来看对象的大小就是16bytes(a是class A的首地址,b是classB的首地址)。有人会问为什么不是20bytes?上面的内存分配偏移是16就说明有20bytes。偏移16,刚好在变量b的下面分配4bytes的C(这说的不准确,不过好明白)这样偏移是16的地方就是D,vbtable现在可以正常定位,Class D为空,就没有必要再分配4bytes的空间,所以sizeof E的大小应该是:vbtable指针(4bytes)
+固定局部(a,b工8bytes)+ C 的4bytes(这有点不好明白,不过下面我还有例子,加深理解)。

代码三:

[cpp] view
plaincopyprint?

class A  

{  

};  

class B  

{  

    int b;  

};  

  

class C  

{  

};  

class D  

{  

};  

class E: public virtual A , public virtual B , public virtual C , public virtual D  

{  

};  

void main()  

{  

   cout<<"sizeof A: "<<sizeof(A)<<endl;  

   cout<<"sizeof B: "<<sizeof(B)<<endl;  

   cout<<"sizeof C: "<<sizeof(C)<<endl;  

   cout<<"sizeof D: "<<sizeof(D)<<endl;  

   cout<<"sizeof E: "<<sizeof(E)<<endl;      //   

}  

上面代码二输出的结果:



这回感觉还是很奇怪吧,看看内存布局吧。



        首先,看看vbtable把,是不是很奇怪,为什么A,B的偏移都是4,ClassA本身就是空,刚好ClassB有一个成员所以肯定需要给classB分配内存的,所以这样就可以找到A的偏移,Class C和Class D就没办法了没办法计算偏移,所以就给ClassC分配了内存,ClassC有了内存这样D就可以的偏移也就出来了,这时候就有人问为什么ClassD的偏移为什么不是8呢(说实话我也没搞懂,我的猜想是对象本身为空,但是为了内存对齐所以就是4bytes。希望懂的大神给以指点,我通过调试多个例子,得出来的,下面的例子就就更好的说明了这点),

代码四:

[cpp] view
plaincopyprint?

class A  

{  

};  

class B  

{     

};  

  

class C  

{  

};  

class D  

{  

};  

class E: public virtual A , public virtual B , public virtual C , public virtual D  

{  

};  

void main()  

{  

    E ee;  

    cout<<"sizeof A: "<<sizeof(A)<<endl;  

    cout<<"sizeof B: "<<sizeof(B)<<endl;  

    cout<<"sizeof C: "<<sizeof(C)<<endl;  

    cout<<"sizeof D: "<<sizeof(D)<<endl;  

    cout<<"sizeof E: "<<sizeof(E)<<endl;      //   

}  

输出结果是:



再看看内存模型吧



         从vbtable来看,里面存储的偏移class E的大小默认固定布局是4bytes,共享布局里面classA、class B和classC的大小是12bytes。所以就是16bytes。

         我也是个菜鸟,也只能这么多了,有不对的地方希望指出来,我也改正下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++