C++虚继承的实现方式与内存布局

说明:本文给出的结论均是在VS2010下调试的结果。

一、问题引入

下面的四个类是典型的C++虚继承的基本结构,现在的问题是这四个类对象的sizeof分别是多少?

class Base{                              //虚基类
public:
	double dou;
};
class Derived1 : public virtual Base{    //虚继承
public:
	double in;
};
class Derived2 : public virtual Base{    //虚继承
public:
	double on;
};
class A : public Derived1, public Derived2{
};

在VS2010下,内存对齐设置为4字节对齐,运行以下代码的结果:

int main()
{
	int i = 0xaabbccdd;
	double a = 1, b = 2, c = 3, d = 4;

	Base bobj;
	bobj.dou = 1;
	Derived1 d1obj;
	d1obj.dou = 2;
	d1obj.in = 1;
	Derived2 d2obj;
	d2obj.dou = 3;
	d2obj.on = 1;
	A aobj;
	aobj.dou = 4;
	aobj.in = 1;
	aobj.on = 1;

	cout << sizeof(bobj) << endl;
	cout << sizeof(d1obj) << endl;
	cout << sizeof(d2obj) << endl;
	cout << sizeof(aobj) << endl;

	system("pause");
}

二、虚基类的内存布局

main函数中的变量i是为了快速定位到栈的存储位置,变量a,b,c,d只是是为了给出double类型的1,2,3,4在内存中的形式,方便后面跟踪各个类对象的成员在内存中的位置。下面的截图是在debug win32下启动调试后得到的栈上的内存布局:

说明:

1、栈上的每个变量间都加入了8个字节的cccccccc cccccccc,这是debug模式下编译器插入的security cookie;

2、黑色框表示的是Base bobj的内存空间,占8字节,存放了double型的变量bobj.dou = 1;

3、红色框表示的是Derived1 d1obj的内存空间,占20个字节:开始的4个字节(0x01387838)是指向虚基类表的指针,后面的八个字节(0x00000000 3ff00000)是double型变量d1obj.in = 1,最后八个字节(0x00000000 40000000)是从虚基类Base继承而来的double型变量d1obj.dou = 2;

4、同理3,黄色框表示的是Derived2 d2obj的内存空间,也是占20个字节:开始的4个字节(0x0138789c)是指向虚基类表的指针,后面的八个字节(0x00000000 3ff00000)是double型变量d2obj.on = 1,最后八个字节(0x00000000 40080000)是从虚基类Base继承而来的double型变量d2obj.dou = 3;

5、绿色框表示的是A aobj的内存空间,共32字节:

开始4个字节(0x013878b4)对应了从Derived1类继承的虚基类表指针,随后的八个字节(0x00000000 3ff00000)是从Derived1中继承的double型变量aobj.in = 1,接下来四字节(0x013878a8)对应了从Derived2类继承的虚基类表指针,之后的八个字节(0x00000000 3ff00000)是从Derived2中继承的double型变量aobj.on = 1,最后八个字节(0x00000000 40100000)则是继承自虚基类Base的double型变量aobj.dou
= 4。

观察以上的内存布局恶意得出以下结论:

1、在虚继承体系中的派生类内存布局的次序是:虚基类表指针,派生类本身的非static成员变量,继承至虚基类的非static成员变量。虚基类指针放在最前面,而从虚基类继承来的成员则在最后面;

2、类A的对象aobj中确实只持有一份虚基类的成员变量,并没有因同时继承了Derived1和Derived2,而持有两份;那么,如果去掉虚继承,改为普通的基础,aobj的内存布局又会是怎样呢?(去掉代码中的两个virtual关键字,调试一下内存布局,可以发现d1obj,d2obj,aobj的内存空间没有虚基类表指针;如果在代码中用到aobj.dou,会编译报错,说“dou的访问不明确”)

看到这里,大家也许会有疑问:凭什么说对象的首字节就是虚基类表指针呢?编译器又是怎么通过虚基类表指针控制A的对象只持有一份虚基类的成员变量的呢?接下来,给出相应的汇编指令加以说明。

三、汇编指令

先看Derived1对象相关部分的汇编代码:

说明:

1、执行完构造函数后,虚基类指针即安放好了;

2、d1obj的头四个字节的内容(0x000A7838)给eax,指向完后发现eax = 686136,也就是16进制的0x000A7838;

3、地址0x000A7838 + 4的内容如下图所示:

4、5、将2放到了d1obj首地址偏移12(0x0000000c)个字节的位置上,也就是对象d1obj内存的最末端。

也就是说:虚基类表中存放了虚基类的成员在派生类内存空间中的偏移量。

再看看A类对象aobj的相关汇编代码:

可以发现大致流程和上一段汇编代码差不多,而且确实只通过头四个字节(从Derived1继承的虚基类指针)取出偏移18字节(aobj对象末尾)的dou变量赋值为4。

eax = 0x000a78b4,[eax + 4]的内容如下:

最后再看看构造函数中是如何安放虚基类表指针的,以Derived1类的构造函数为例:

时间: 2024-10-07 12:29:14

C++虚继承的实现方式与内存布局的相关文章

多继承(虚继承)派生类对象内存结构

在这里谈一下虚继承.前面写过派生类对象的内存结构,都是基于VS2010编译器的,不同的编译器对于继承的处理不同,但本质都是一样的. 虚继承是解决共享基类问题的.例如在菱形继承中 如果不使用虚继承,基类A在D中会有两个,这不仅浪费内存,还会造成歧义.使用虚继承就可以解决基类共享的问题. 要想在派生类中共享基类(例如在D对象中只有一个A对象,这时候D对象中的B对象和C对象都可以查找到A,而不是在B对象和C对象中各含有一个A对象). 先看下面一个例子: #include<iostream> usin

虚表结构与虚继承内存对象模型

最近看了下Inside C++里面讲的对虚继承层次的对象的内存布局,发现在不同编译器实现有所区别.因此,自己动手探索了一下.结果如下: 首先,说说GCC的编译器. 它实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针. class A { int a; virtual ~A(){} }; class B:virtual public A{ virtual ~B(){} virtual void myfunB(){} }; class C:virt

【整理】C++虚函数及其继承、虚继承类大小

参考文章: http://blog.chinaunix.net/uid-25132162-id-1564955.html http://blog.csdn.net/haoel/article/details/1948051/ 一.虚函数与继承 1.空类,空类单继承,空类多继承的sizeof #include <iostream> using namespace std; class Base1 { }; class Base2 { }; class Derived1:public Base1

C++各种类继承关系的内存布局

body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;} th{border: 1px solid gray; padding: 4px; background-color: #DDD;} td{border: 1px solid gray; padding: 4px;} tr:nth-child(

【转载】图说C++对象模型:对象内存布局详解

原文: 图说C++对象模型:对象内存布局详解 正文 回到顶部 0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看.本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有所不同.文章如果有解释不清.解释不通或疏漏的地方,恳请指出. 回到顶部 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各种支持的底层

c++ 对象内存布局详解

今天看了的,感觉需要了解对象内存的问题. 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各种支持的底层实现机制. 直接支持面向对象程序设计,包括了构造函数.析构函数.多态.虚函数等等,这些内容在很多书籍上都有讨论,也是C++最被人熟知的地方(特性).而对象模型的底层实现机制却是很少有书籍讨论的.对象模型的底层实现机制并未标准化,不同的编译器有一定的自由来设计对象模型的实现细节.在我看来,对

多重虚继承下的对象内存布局

<深入C++对象模型>绝对是一本值得深读的一本书,书里多次出现一句话,“一切常规遇见虚继承,都将失效”.这是一个有趣的问题,因为C++标准容忍对象布局的实现有较大的自由,出现了各编译器厂商实现的方式不同. 今天谈谈visual studio2013多重虚继承下对象布局.有错不要客气,不要吝啬你的留言,请直接开喷. class y和class z都是从class x虚继承来的子类(也叫派生类),class A是class y和class z的多重继承子类.为了简化问题,下面的data membe

C++ 对象的内存布局—— 虚继承下的虚函数

C++ 对象的内存布局(下)这篇文章的"单一虚拟继承"和"钻石型虚拟继承"时的类内存布局讲得不太清楚,我有一处疑问,我用的是VS2005.因此记录一下. 类继承图例如以下: 这里:类B被类B1和B2虚拟继承,而B1和B2同一时候被D继承. B1的f().B2的f()覆盖了B的f(): D的f()覆盖了B1的f(),D的f1()覆盖了B1的f1() D的f()覆盖了B2的f(),D的f2()覆盖了B2的f2() 类代码例如以下: class B { public: i

C++中虚函数工作原理和(虚)继承类的内存占用大小计算

一.虚继承情况下类的内存大小计算 当每个基类中有多个虚函数时,并且在虚继承的情况下,内存是如何分配的,如何计算类的大小,下面举例说明: #include<iostream> using namespace std; class A { public: int a; virtual void aa(){}; }; class D { public: virtual void dd(){}; }; class C { public: virtual void cc(){}; }; class B