关于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++对象内部模型用图片表示如下:

从图中可以看出,在派生类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关键字即可。实现代码如下:

 View Code

使用cl编译器查看钻石型虚拟重复继承中的C++对象内存模型结果如下图所示:

从上图可以看出,虚拟重复继承中的派生类D对象在内存中占有52字节,比之前多了8个字节。

虚拟重复继承中的C++对象内部模型用图片表示如下:

从图中可以看出,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.

也就是说如果虚拟继承中派生类重写了基类的虚函数,并且在构造函数或者析构函数中使用指向基类的指针调用了该函数,编译器会为虚基类添加vtordisp域。

但在本例中,vtordisp为什么存在于C++派生类对象中,对象如何使用它,我却不得而知,希望向大家请教。

至此,我们已经分析完在VC++编译器实现中的重复继承和钻石型虚拟重复继承的C++对象内存模型。这篇博客也花了大概4个小时,时间挺久,但非常值得,希望大家多多指教。

转载于:https://blog.51cto.com/10622551/1696424

钻石继承和虚钻石继承的对象模型相关推荐

  1. C++中的继承与虚函数各种概念

    虚继承与一般继承 虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段.而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个 ...

  2. 「现代C++设计魅力」虚函数继承-thunk技术初探

    简介:工作中使用LLDB调试器调试这一段C++多继承程序的时候,发现通过lldb print(expression命令的别名) 命令获取的指针地址和实际理解的C++的内存模型的地址不一样.那么到底是什 ...

  3. c/c++入门教程 - 2.4.6 继承、公共继承、保护继承、私有继承、virtual虚继承(概念、语法、方式、构造和析构顺序、同名成员处理、继承同名静态成员处理、多继承语法、菱形继承、钻石继承)

    目录 4.6 继承 4.6.1 继承的基本语法 4.6.2 继承方式 4.6.3 继承中的对象模型 4.6.4 继承中构造和析构顺序 4.6.5 继承同名成员处理方式 4.6.6 继承同名静态成员处理 ...

  4. C++:钻石继承与虚继承

    QUESTION:什么是钻石继承? ANSWER:假设我们已经有了两个类Father1和Father2,他们都是类GrandFather的子类.现在又有一个新类Son,这个新类通过多继承机制对类Fat ...

  5. C++_类和对象_C++继承_菱形继承_或钻石继承_问题及利用虚继承解决该问题---C++语言工作笔记068

    然后我们再来看一下在c++继承中的,一个很好玩的内容, 菱形继承,又叫 钻石继承. 比如我们有个动物类,然后 羊继承了这个动物类,然后驼也继承了这个动物类, 然后,羊驼,通过多继承,继承了羊类和驼类, ...

  6. 菱形继承和虚继承、对象模型和虚基表

    1.菱形继承(钻石继承):两个子类继承同一父类,而又有子类同时继承这两个子类.例如B,C两个类同时继承A,但是又有一个D类同时继承B,C类. 2.菱形继承的对象模型 class A { public: ...

  7. C++对象模型:单继承,多继承,虚继承

    什么是对象模型 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各种支持的底层实现机制. 类中成员分类 数据成员分为静态和非静态,成员函数有静态非静态以及虚函数 cla ...

  8. python---之super()继承,解决钻石继承难题

    1.   Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通方法和super方法 假设Base是基类 class Base(object):def __init__ ...

  9. python 钻石继承_python3--object类,继承与派生,super方法,钻石继承问题

    python3--object类,继承与派生,super方法,钻石继承问题 发布时间:2018-04-13 20:38:05编辑:Run阅读(1914) 昨天内容复习 组合:什么有什么的关系(例:老师 ...

最新文章

  1. Bigtable:结构化数据的分布式存储系统
  2. 第25节 典型应用集成技术
  3. leetcode 877. 石子游戏(dp)
  4. doom 源码_Cartpole和Doom的策略梯度简介
  5. C++11:右值引用和转移赋值
  6. CGAL4.4+VC2008编译
  7. java怎么播放视频_如何播放视频文件 java
  8. linux下录音识别成文字软件下载,如何将录音转换成文字,这个方法你需要知道...
  9. 大小写字母ASCII码对照表
  10. 多元线性方程的几种解法
  11. 动态规划-不相邻数字之和的最大值
  12. 计算机处理器缓存参数,如何查看CPU型号和主频、缓存、接口等参数
  13. 源支付3.1版本全开源版+店员监控软件+手机监控APP源码
  14. 认识控制台-什么是控制台?
  15. 浙北山村“洋家乐”:中西结合乡村旅游成脱贫新产业
  16. wps如何放大导航窗格字体
  17. 通过掩码计算IP范围
  18. 视频帧差法实例(matlab实现)
  19. 【Mysql】安装tokudb引擎
  20. 语音翻译中文并不难,我来推荐语音翻译软件哪个好用

热门文章

  1. 再树行业标杆:华硕笔记本发布多款重磅新品
  2. BZOJ 2002 Bounce 弹飞绵羊 [分块]
  3. [UWP] 模仿哔哩哔哩的一键三连
  4. JavaAES128对称加密算法实现
  5. 康耐视visionpro定位引导标定简介及方法
  6. PSO算法优化应用实例(2020.09.24)
  7. 浮点数c语言,c语言中的浮点数
  8. 应用范例:解析 Yahoo 奇摩股市的各档股票资讯-HtmlAgilityPack
  9. K均值算法分析与实现(附源码)
  10. Python数据分析,学习路径拆解及资源推荐(附详细思维导图)