对虚继承层次的对象的内存布局,在不同编译器实现有所区别。

首先,说说GCC的编译器.

它实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针。

class A {

int a;

virtual ~A(){}

};

class B:virtual public A{

virtual void myfunB(){}

};

class C:virtual public A{

virtual void myfunC(){}

};

class D:public B,public C{

virtual void myfunD(){}

};

以上代码中 sizeof(A)=8,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16.

解释:A中int+虚表指针。B,C中由于是虚继承因此大小为A+指向虚基类的指针,B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针。D由于B,C都是虚继承,因此D只包含一个A的副本,于是D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针。

如果B,C不是虚继承,而是普通继承的话,那么A,B,C的大小都是8(没有指向虚基类的指针了),而D由于不是虚继承,因此包含两个A副本,大小为16. 注意此时虽然D的大小和虚继承一样,但是内存布局却不同。

然后,来看看VC的编译器

vc对虚表指针的处理比GCC复杂,它根据是否为虚继承来判断是否在继承关系中共享虚表指针,而对指向虚基类的指针和GCC一样是不共享,当然也不可能共享。

代码同上。

运行结果将会是sizeof(A)=8,sizeof(B)=16,sizeof(C)=16,sizeof(D)=24.

解释:A中依然是int+虚表指针。B,C中由于是虚继承因此虚表指针不共享,由于B,C加入了自己的虚函数,所以B,C分别自己维护一个虚表指针,它指向自己的虚函数。(注意:只有子类有新的虚函数时,编译器才会在子类中添加虚表指针)因此B,C大小为A+自己的虚表指针+指向虚基类的指针。D由于B,C都是虚继承,因此D只包含一个A的副本,同时D是从B,C普通继承的,而不是虚继承的,因此没有自己的虚表指针。于是D大小就等于A+B的虚表指针+C的虚表指针+B中的指向虚基类的指针+C中的指向虚基类的指针。

同样,如果去掉虚继承,结果将和GCC结果一样,A,B,C都是8,D为16,原因就是VC的编译器对于非虚继承,父类和子类是共享虚表指针的。

以下例子都是在VC上实验过的:

例一:

class A {
int a;
virtual ~A(){}
};

class B:virtual public A{

virtual void myfunB(){};

};

class BB: public A{

virtual void myfunB(){};

};
class C:virtual public A{

virtual void myfunC(){}

};
class CC:virtual public A{

virtual void myfunC(){}

};
class D:public B,public C{

virtual void myfunD(){}

};

class DD:public BB,public CC{

virtual void myfunD(){}

};

class DDD:public BB,public C{

virtual void myfunD(){}

};
class D0:virtual public B,public C{

virtual void myfunD(){}

};
class D00:virtual public B,virtual public C{

virtual void myfunD(){}

};
class D1:virtual public BB, public CC{

virtual void myfunD(){}

};
class D11:virtual public BB,virtual public CC{

virtual void myfunD(){}

};

cout<<sizeof(A)<<"\n"<<sizeof(B)<<'\n'<<sizeof(C)<<'\n'<<sizeof(D)<<endl;  //8  16  16  24

cout<<sizeof(BB)<<'\n'<<sizeof(CC)<<'\n'<<sizeof(DD)<<'\n'<<sizeof(DDD)<<endl;//8 16 24 24

cout<<sizeof(D0)<<'\n'<<sizeof(D00)<<'\n'<<sizeof(D1)<<'\n'<<sizeof(D11)<<endl;//24 32 24 32

例二:

1.class K{};                         sizeof(K) = 1;

2. class A{ int a;   void a();   int aa();};      sizeof(A) = 4;

3. class B{   int b;   virtual void b();   };       sizeof(B) = 4+4;

4. class C0:public A,B { };               sizeof(C)=12

class C1:public B{ int c;  virtual void c(); };  sizeof(C1)=4+4+4=12

class C:virtual public B{int c; virtual void c();}sizeof(C)=4+4+4+4+4=20 //虚继承时VC中基类和子类不共享虚指针

5. class D:virtual public B{int d; virtual void d();}sizeof(D)=4+4+4+4+4=20 //虚继承时VC中基类和子类不共享虚指针

6. class E:public C,public D{ int e;}    sizeof(E) = 36//B+C与D的两个虚指针+C与D的本身成员+E成员

class E: virtual public C,public D{  int e; }  sizeof(E) = 36

class E: virtual public C, virtual public D{  int e;}   sizeof(E) =36

例三:

class A{};
class B
{
 int a;
 char c;
 static int s;
};
class C
{
 int d;
 virtual void f();
};
class D:virtual public B
{
 double dd;
};
class E:public C
{
 virtual void f1();
};
class F:virtual public B
{
 
};
class G:public D,F
{

};
int a[20];
int *p;
double *da[20];

cout<<"double类型   "<<sizeof(double)<<endl;//double类型占有8个字节
 cout<<"整型数组  "<<sizeof(a)<<endl;//数组占有4*20个字节
 cout<<"int类型指针 "<<sizeof(p)<<endl;//指针占有4个字节
 cout<<"double指针数组  "<<sizeof(da)<<endl;//double指针类型的数组占有4*20个字节,因为指针式占有四个字节的
 cout<<"empty class "<<sizeof(A)<<endl;//空类占有1个字节,因为每个实例在内存中都有一个独一无二的地址,
为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址
 cout<<"带有两个普通成员一个静态成员的类   "<<sizeof(B)<<endl;//B类的大小为8,因为内存对齐,所以c后面有3个字节是空的,而s是static类型属于全局数据区的
 cout<<"带有虚函数的类   "<<sizeof(C)<<endl;//int型4个字节,虚指针4个字节 8
 cout<<"虚拟继承 "<<sizeof(D)<<endl;//double占8个字节,基类中的int和char类型共占8个字节,多三个字节空着,虚指针占4个字节,多4个空着为了字节对齐  24
 cout<<"基类中有虚函数,本类中也有虚函数  "<<sizeof(E)<<endl;//基类C中的int型占四个字节,基类和派生类中的虚函数共用一个虚指针占用4个字节 8
 cout<<"虚拟继承的类 "<<sizeof(F)<<endl;//从基类中派生过来的成员有8个字节,虚指针4个字节,共12个字节
 cout<<"多重继承 "<<sizeof(G)<<endl;//从基类中得到的成员共D类中double8个字节,B类中int和char共8个字节,D类和F类中分别8个虚指针字节所以共32个字节

总结:sizeof 类 = 成员变量+虚函数指针(void变量 static变量 普通函数 均不算在内)。

sizeof 继承类 = 本身成员变量+父类的大小,如果是n个平级虚拟继成的话,则另外加n个指向父类指针的大小;而对于虚拟成员变量,一般情况下共享一个虚拟指针,VC平台上虚继承时父类和子类虚表指针不共享(比非虚继承时大小大的多)。

多重继承的时候(有多层继承级别),若通过不同继承渠道有相同的父类,渠道含虚继承则只得到一套此父类的成员,不含虚继承则可能得到若干套此父类的成员

sizeof与类,继承,virtual的种种的更多相关文章

  1. sizeof(类)

    类的大小是什么?确切的说,类只是一个类型定义,它是没有大小可言的. 用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小.首先:我们要知道什么是类的实例化,所谓类的实例化就是在内存中分配 ...

  2. C++父子类继承时的隐藏、覆盖、重载

    存在父子类继承关系时,若有同名成员函数同时存在,会发生隐藏.覆盖和重载这几种情况.对于初学者也比较容易混淆,为此,我整理了一下我的个人看法,仅供参考.希望对大家理解有帮助,也欢迎指正. 1.父子类继承 ...

  3. C++对象内存分布(3) - 菱形继承(virtual)

    1.前言 本篇文章的全部代码样例.假设是windows上编译执行.则使用的是visual studio 2013.假设是RHEL6.5平台(linux kernal: 2.6.32-431.el6.i ...

  4. C++ 类继承的对象布局

    C++多重继承下,对象布局与编译器,是否为虚拟继承都有很大关系,下面将逐一分析其中的差别,相同点为都按照类继承的先后顺序布局(类内按照虚表.成员声明先后顺序排列).该类情况为子类按照继承顺序排列,如c ...

  5. C++——类继承

    类库:类库由类声明和实现构成.类组合了数据表示和类方法,因此提供了比函数库更加完整的程序包. 类继承:从已有的类派生出新的类,派生类继承了原有类(称为基类)的特征,包括方法. 通过类继承可以完成的工作 ...

  6. C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类

    类继承 在C++类继承中,一个派生类可以从一个基类派生,也可以从多个基类派生. 从一个基类派生的继承称为单继承:从多个基类派生的继承称为多继承. //单继承的定义 class B:public A { ...

  7. 【07】为多态基类声明virtual析构方法

    1.考虑下面的需要,需要一个工厂方法.工厂方法的规则是:在堆上分配一个子类对象,并返回父类指针.使用完毕,delete父类指针.如果父类的析构方法不是virtual,将直接调用父类的析构方法,导致局部 ...

  8. sizeof求类的大小

    用sizeof求类的大小,http://blog.csdn.net/szchtx/article/details/10254007(sizeof浅析(三)——求类的大小),这篇博文给出了非常详尽的举例 ...

  9. C++基础——类继承中方法重载

    一.前言 在上一篇C++基础博文中讨论了C++最基本的代码重用特性——类继承,派生类可以在继承基类元素的同时,添加新的成员和方法.但是没有考虑一种情况:派生类继承下来的方法的实现细节并不一定适合派生类 ...

  10. c++中的类(class)-----笔记(类继承)

    1,派生类继承了基类的所有成员函数和数据成员(构造函数.析构函数和操作符重载函数外). 2,当不指明继承方式时,默认为私有继承. 3,基类的私有成员仅在基类中可见,在派生类中是不可见的.基类的私有成员 ...

随机推荐

  1. MVC每层的职责

    MVC模式把应用程序分割成三层:模型.视图和控制器. 模型:模型代表着核心的业务逻辑和数据.模型封装了域实体的属性和行为,并暴露出了实体的属性. 视图: 视图负责转换模型并把它传递给表示层.视图应关注 ...

  2. Cross-compiling Qt Embedded 5.5 for Raspberry Pi 2

    This tutorial shows how to cross-compile the Embedded build of Qt 5.5 for Raspberry Pi 2. The Embedd ...

  3. 常用的Windows批处理

      切换执行路径 如果不换盘的话:cd xxx换盘:cd /d xxx   获取当前日期 编写Windows批处理时经常会需要使用到日期和时间作为文件名,所以是非常重要的. 如何获取日期呢?格式:  ...

  4. MVC4.0 上传Excel并存入数据库

    这里的这个功能实现在WebForm很好实现,上传阶段简单的一个FileUoLoad控件就搞定了,什么取值,什么上传都是浮云,微软都帮我们封装好了,我们只需要一拖一拽就OK了,但这些在MVC中是不行的! ...

  5. c# splitter控件使用简介

    摘自:http://blog.itpub.net/26221264/viewspace-735903 1.先在窗体上放置部分一的控件,这里是TreeView控件,然后把它的 Dock 属性设置为 Le ...

  6. 黑马程序员_Java面向对象2_继承

    4.面向对象_继承 4.1继承的概述 提高了代码的复用性. 让类与类之间产生了关系,有了这个关系,才有多态的特性. 注意:千万不要为了获取其他类的功能而去继承,简化代码而继承.必须是类与类之间有所属关 ...

  7. wikioi3052 多米诺

    题目描述 Description 一个矩形可以划分成M*N个小正方形,其中有一些小正方形不能使用.一个多米诺骨牌占用两个相邻的小正方形.试问整个区域内最多可以不重叠地放多少个多米诺骨牌且不占用任何一个 ...

  8. Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile 解决办法

    Maven install失败 Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (d ...

  9. 关于C#中的弱引用

    本文前部分来自:http://www.cnblogs.com/mokey/archive/2011/11/24/2261605.html 分割线后为作者补充部分. 一:什么是弱引用 了解弱引用之前,先 ...

  10. openwrt上网配置的一些理解

    其实已经有很多帖子讲过openwrt路由器上网配置了,我这里主要是讲我自己的一块硬件路由使用openwrt后的一些上网配置.之所以要研究我自己的配置,是因为硬件,硬件不一样,配置也就不一样,但是总的原 ...