钻石型继承模型的内存分布
来源:互联网 发布:知乎洗头发顺序 编辑:程序博客网 时间:2024/04/26 03:04
转自:点击打开链接 和点击打开链接 并更正一个小错误以及增加了自己的一些理解
关于C++对象内存布局的资料和书籍也有很多,比如陈皓老师的博客:
1、C++对象的内存布局(上)
2、C++对象的内存布局(下)
白杨:
RTTI、虚函数和虚基类的实现方式、开销分析及使用指导
左手为你画猜:
C++类对象内存模型与成员函数调用分析(上、中、下)
关于讲解C++对象内存模型最好的书应该是侯捷老师翻译的《深度探索C++对象内存模型》。
这两天在看其他书籍时,对C++中虚拟继承的实现机制不太理解,于是又重新翻回《深度探索C++对象内存模型》一书,并结合C++对象的内存布局(下)一文。在Visual Studio 2010下用“cl”编译器进行测试,查看虚拟多重继承下的C++对象内存模型。总结如下:
一、重复继承
所谓重复继承,即某个基类被间接地重复继承了多次。为方便对比说明,下面的代码采用了陈皓老师博客中C++类例子。
UML类图如下:
类继承的源代码如下,直接采用C++对象的内存布局(下)中的例子,相关解释已在原博客中详细说明,故在此不再赘述:
1 #include <iostream> 2 using namespace std; 3 4 class B 5 { 6 public: 7 int ib; 8 char cb; 9 public:10 B():ib(0),cb('B')11 {}12 virtual void f()13 {14 cout<<"B::f()"<<endl;15 }16 virtual void Bf()17 {18 cout<<"B::Bf()"<<endl;19 }20 };21 22 class B1:public B23 {24 public:25 int ib1;26 char cb1;27 public:28 B1():ib1(01),cb1('1'){}29 30 virtual void f()31 {32 cout<<"B1::f()"<<endl;33 }34 virtual void f1()35 {36 cout<<"B1::f1()"<<endl;37 }38 virtual void Bf1()39 {40 cout<<"B1::Bf1()"<<endl;41 }42 };43 44 class B2:public B45 {46 public:47 int ib2;48 char cb2;49 public:50 B2():ib2(10),cb2('2'){}51 virtual void f()52 {53 cout<<"B2::f()"<<endl;54 }55 virtual void f2()56 {57 cout<<"B2::f2()"<<endl;58 }59 virtual void Bf2()60 {61 cout<<"B2::Bf2()"<<endl;62 }63 };64 65 class D: public B1, public B266 {67 public:68 int id;69 char cd;70 public:71 D():id(100),cd('D'){}72 73 virtual void f()74 {75 cout<<"D::f()"<<endl;76 }77 virtual void f1()78 {79 cout<<"D::f1()"<<endl;80 }81 virtual void f2()82 {83 cout<<"D::f2()"<<endl;84 }85 virtual void Df()86 {87 cout<<"D::Df()"<<endl;88 }89 90 };91 int main(int argc, char *argv[])92 {93 D d;94 system("pause");95 return 0;96 }
在陈皓老师博客中,直接利用函数指针调用C++对象起始位置处虚函数表指针指向的虚函数表中的虚函数,以查看C++对象的内存模型。下面我们主要采用Visual Studio 2010 和 Visual C++下的“cl”编译器查看C++对象内存模型。
在Visual Studio 2010 IDE开发环境中,我们查看派生类D对象的内存模型。如下图所示:
从上两图我们可以基本看出:
1、派生类D对象d的内存布局中,由其基类依次组装而成,再加上派生类自己的成员变量。
2、其中基类布局依次按照在派生类中的声明顺序排列。
3、每个基类都有自己的虚函数表,指向虚函数表的指针_vfptr放置在最前面的位置。
为了再进一步了解重复继承中的C++对象内存模型,我们采用Visual C++下的“cl”编译器进行查看。
在“Microsoft Visual C++”的编译环境中,我们可以利用编译器“cl”、链接器“link”、可执行文件查看器“dumpbin”来查看Windows下可执行文件(COFF格式)的变量、函数怎么存储。
“cl”即Visual C++ 的编译器,即“Compiler”的缩写。在Visual Studio 2010安装完后,会有一个批处理文件用来建立运行这些工具所需要的环境。它位于开始/程序/Microsoft Visual Studio 2010/Visual Studio Tools/Viusual Studio 2010 Command Prompt,这样我们就可以利用命令行使用VC++的编译器了。
在“cl”编译器中有个编译选项可以查看C++类的内存布局,使用如下:打开Visual Studio的命令行提示符即Viusual Studio 2010 Command Prompt,按如下格式输入:
>cl [.cpp] /d1reportSingleClassLayout[classname]
d1reportSingleClassLayout可以查看源文件中所有类及结构体的内存布局,classname为类名,/d1reportSingleClassLayout[classname]之间没有空格。使用如下图所示:
使用cl编译器查看重复继承中的C++对象内存模型结果如下图所示:
从上图可以看出,编译器在实现时使用了字节对齐(Alignment),以实现在对象内存中存取更有效率。字节对齐就是将数值调整到某数的整数倍,在32位计算机中,通常Alignment为4bytes,以使bus的“运输量”达到最高效率。
可以看出,派生类D对象在内存中占有44个字节。
重复继承中的C++对象内部模型用图片表示如下:
其中第一个vfptr指向的虚表是B1和D共享的,因此其中的函数接口应该覆盖了B1和D,而第二个则只有B2的虚表。
从图中可以看出,在派生类D中,存在着两份基类B的成员实例,分别为ib和cb,所以在C++对象的内存布局(下)指出这样可能会出现二义性编译错误。我们可以指定类作用域符::进行限定来消除二义性,也可以在语言层面利用虚拟继承机制来解决。
二、钻石型多重虚拟继承
在《深度探索C++对象模型》中提到:一个virtual base class subobject只会在derived class中存在一份实体,不管它在class继承体系中出现多少次!
因此,虚拟继承的就是为了解决重复继承中多个间接父类的问题。钻石型的结构就是最经典的虚拟多重继承结构。
UML类图如下:
如上图,让B1和B2各自维护的一个B子对象,折叠成一个由D维护的单一的B子对象,并且还可以保存基类和派生类的指针之间的多态指定操作,这对于编译器实现来说,难度非常高。《深度探索C++对象模型》提到一般的实现方法如下所述:将D对象分割为两部分,一个不变局部和一个共享局部。不变局部中的数据,不管后继如何衍化,总是拥有固定的偏移量,所以这一部分数据可以被直接存取,至于共享局部,所表现的就是虚拟继承的基类子对象,这一部分的数据,其位置会因为每次的派生操作而有变化,所以它们是间接存取。
所以,一般的布局策略是安排好派生类对象的不变部分,然后再建立其共享部分。在接下来的分析可以看出,VC++编译器实现中,在每一个派生类对象中插入一些指针vbptr,每个指针指向一个虚拟继承的基类子对象。要存取继承得来的基类子对象,可以使用相关指针间接完成。
要实现虚拟继承,我们只需要在B1和B2继承B的语法中加入virtual关键字即可。实现代码如下:
使用cl编译器查看钻石型虚拟重复继承中的C++对象内存模型结果如下图所示:
从上图可以看出,虚拟重复继承中的派生类D对象在内存中占有52字节,比之前多了8个字节。
虚拟重复继承中的C++对象内部模型用图片表示如下:
注意和非虚多重继承不同的是,第一个虚表只记录了B1(不包括B)和D的函数接口,B的函数接口在最后的基类虚表中,另外B1和B2部分各有一个vbptr。
从图中可以看出,VC++编译器在实现虚拟继承时,在派生类的对象中安插了两个vbptr指针。因此,对每个继承自虚基类的类实例,将增加一个隐藏的“虚基类表指针”(vbptr)成员变量,从而达到间接计算虚基类位置的目的。该变量指向一个全类共享的偏移量表,表中项目记录了对于该类而言,“虚基类表指针”与虚基类之间的偏移量。由上可以看出,B1虚基类表指针vbptr与虚基类B之间的偏移量是40字节,B2虚基类表指针vbptr与虚基类B之间的偏移量是24字节。第一项中-4的含义:表示的是vptr和vbptr的距离,如果B1中没有虚函数的定义,这个地方就会是0。vbptr就是存放在vptr下面的位置。
我们注意到在虚拟继承的C++对象内存布局中,还有一个4个字节的vtordisp字段,vtordisp在MSDN中这样解释:
Enables the addition of the hidden vtordisp construction/destruction displacement member. The vtordisp pragma is applicable only to code that uses virtual bases. If a derived class overrides a virtual function that it inherits from a virtual base class, and if a constructor or destructor for the derived class calls that function using a pointer to the virtual base class, the compiler may introduce additional hidden “vtordisp” fields into classes with virtual bases.
#include "stdafx.h"#include <iostream> using namespace std;class Point{public:Point(int x = 1, int y = 1) :_x(x), _y(y){}virtual void print(){ cout << "This is Point. "; }protected:int _x, _y;};class Point3d :virtual public Point{public:Point3d() :_z(2){}void print(){ cout << "This is Point3d. "; }protected:int _z;};int main(){Point3d d;int *p = (int *)&d;p++;cout << *p << endl;//输出_z的值2 p++;cout << *p << endl; //输出vtordisp的值,这里为0 p++;cout << *p << endl; //输出vptr的值 p++;cout << *p << endl;//输出_x的值 1 p++;cout << *p << endl; //输出_y的值1 cout << sizeof(Point3d) << endl; //大小为24 system("pause");return 0;}
#pragma vtordisp( off )class GetReal : virtual public { ... };#pragma vtordisp( on )
class B{public:virtual void fooB(){}virtual void funcB(){}int mB=0;};class B1 :public virtual B{public:B1() :mB1(1){}void fooB(){}virtual void fooB1(){}int mB1;};class B2 :public virtual B{public:B2() :mB2(2){}void funcB(){}virtual void fooB2(){}int mB2;};class D :public B1, B2{D() :mD(3){ }virtual void fooD(){}int mD;};则此时内存模型为:
注意:vtordisp域的生成只看派生类(包括D)是否对虚基类(B)的虚函数有重写,并且有显式构造/析构函数(可能调用虚函数),例如如下代码:
class B{public:virtual void fooB(){}virtual void funcB(){}int mB=0;};class B1 :public virtual B{public://B1() :mB1(1){}void fooB(){}virtual void fooB1(){}int mB1;};class B2 :public virtual B{public://B2() :mB2(2){}void funcB(){}virtual void fooB2(){}int mB2;};class D :public B1, B2{D() :mD(3){ }void fooB(){}virtual void fooD(){}int mD;};仍然会生成vtordisp:
- 钻石型继承模型的内存分布
- 钻石继承中的类的内存分布
- 钻石型虚拟多重继承的C++对象内存模型
- 钻石型虚拟多重继承的C++对象内存模型
- C++类内存分布+钻石模型的解决方法
- 继承的内存分布和代价
- C++中继承的内存分布
- C++中继承的内存分布
- C++继承多态下的内存分布
- c++多重继承的内存分布
- 虚继承下对象的内存分布
- 继承 内存分布
- 继承 内存分布
- 继承 内存分布
- 继承 内存分布
- 继承 内存分布
- 多重继承及虚继承中对象内存的分布
- 多重继承及虚继承中对象内存的分布
- 线程和进程
- Linux下MJPG-Streamer视频服务器搭建
- 第二次CCF计算机软件能力认证考试题解(Java)--201409--字符串匹配--100分通过
- 在浏览器输入一个网址,如http://www.taobao.com,按回车之后发生了什么?
- 内部碎片和外部碎片
- 钻石型继承模型的内存分布
- 在调用C这个非托管语言不要进行任何托管安全检查
- JSP自定义标签
- Android四大组件之一内容提供者
- ios核心动画 类图 一目了然
- ps命令详解
- RAM和ROM的一些却别
- 有只小猴子,路边有80根香蕉,小猴子走40步能到家,每走一步要吃1根香蕉,否则就停止不前,小猴子每次能搬40根 香蕉,请编程求出小猴子最多能搬多少根香蕉回到家。
- NoClassDefFoundError: org/json/JSONException解决方法