转自:http://blog.csdn.net/jiangyi711/article/details/4890889#

一 类布局

不同的继承方式将导致不同的内存布局

1)C结构

C++基于C,所以C++基本上兼容C。特别地,C++规范在“结构”上使用了和C相同的,简单的内存布局原则:成员变量按其被声明的顺序排列,按具体实现所规定的对齐原则在内存地址上对齐。

struct A {
char c;
int i;
};

从上图可见,A在内存中占有8个字节,按照声明成员的顺序,前4个字节包含一个字符(实际占用1个字节,3个字节空着,补对齐),后4个字节包含一个整数。A的指针就指向字符开始字节处。

2)有C++特征的结构:

C++本质上是面向对象的语言:包含继承、封装,以及多态

原始的C结构经过改造,成了面向对象世界的基石——类。

除了成员变量外,C++类还可以封装成员函数和其他东西。

C++类实例的大小完全取决于一个类及其基类的成员变量,以及为了实现虚函数和虚继承而引入的隐藏成员变量。成员函数基本上不影响类实例的大小。

struct B {
public:
int bm1;
protected:
int bm2;
private:
int bm3;
static int bsm;
void bf();
static void bsf();
typedef void* bpv;
struct N { };
};

这里B是一个C结构,然而,该结构有一些C++特征:控制成员可见性的public/protected/private关键字、成员函数、静态成员,以及嵌套的类型声明

实际上,只有成员变量才占用类实例的空间 

类中的成员函数存放在代码区,静态函数也存放在代码区,而不是静态区。静态成员函数与一般成员函数的唯一区别就是没有this指针,因此不能访问非静态数据成员

B中,为何static int bsm不占用内存空间?因为它是静态成员,该数据存放在程序的数据段中,不在类实例中

3)单继承

struct C
{
int c1;
void cf();
}; struct D : C
{
int d1;
void df();
};

派生类要保留基类所有的属性和行为,每个派生类的实例都包含了一份完整的基类实例数据

在D中,并不是说基类C的数据一定要放在D的数据之前,只不过这样放的话,能够保证D中的C对象地址,恰好是D对象地址的第一个字节

在这种安排下,有了派生类D的指针,要获得基类C的指针,就不必要计算偏移量了

即在单继承模式下,每个派生类都简单的把自己的成员变量添加到基类的成员变量之后

4)多重继承

struct C {
int c1;
void cf();
}; struct E {
int e1;
void ef();
}; struct F : C , E {
int f1;
void ff();
};

机构F从C和E多重继承得来,与单继承不同的是,F实例靠内了每个基类的所有数据。

与单继承不同的是,在多重继承下,内嵌的两个基类的对象指针不可能全都与派生类对象指针相同

VC++按照基类的声明顺序,先排列基类实例数据,最后才排列派生类实例数据,派生类数据本身也是按照声明顺序布局的(在有虚函数的情况下,这个规则有所不同)

5)虚继承

 考虑下面这种继承层次:

struct A {};
struct B :A {};
struct C :A {};
struct D :B ,C {};

则在D的实例中,将包含两个A的实例,这两个实例分别来自B和C,这导致了额外的内存开销,并且会造成混乱(对于D,不知道如何区分两个A的实例)

所以出现了虚继承

struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B , C {};

使用虚继承,比单继承和多重继承将有更大的实现开销和调用开销:

在单继承或多重继承下,内嵌的基类实例地址与派生类的实例地址相比,要么地址相同,要么相差一个固定的偏移量

当虚继承时,一般说来,派生类地址和其虚基类地址之间的偏移量是不固定的,因为派生类如果被进一步继承的话,最终派生类会把共享的虚基类实例数据放到一个与上一层派生类不同的偏移量处:

struct G : virtual C {
int g1;
void gf();
};

vbptr虚基类表指针:

GdGvbptrG:在G中G对象的指针与G的虚基类表指针之间的偏移量,在此可见为0,因为G对象内存布局第一项就是虚基类表指针

GdGvbptrC:在G中C对象的指针与G的虚基类表指针之间的偏移量,在此可见为8

struct H : virtual C {
int h1;
void hf();
};

struct I : G, H {
int i1;
void _if();
};

从上面这些图可以看出

在G对象中,内嵌的C基类的数据紧跟在G的数据之后,在H对象中,内嵌的C基类对象的数据紧很在H的数据之后,但在I对象中,内存的布局并非如此

在VC++中,对每个继承自虚基类的类实例,将增加一个隐藏的虚基类表指针成员变量,从而达到间接计算虚基类位置的目的。该变量指向一个全类共享的偏移量表,表中记录了对于该类而言,虚基类表指针与虚基类之间的偏移量

可以得到下列关于VC++虚拟继承下内存布局的结论:

1):首先排列非虚继承的基类实现

2):有虚基类时,为每个基类增加一个隐藏的vbptr指针,除非已经从非虚继承的类那里继承了一个vbptr

3):排列派生类的数据成员

4):在实例最后,排列每个虚基类的一个实例

C++类继承内存布局(一)的更多相关文章

  1. C++类继承内存布局(三)

    参考:http://blog.csdn.net/jiangyi711/article/details/4890889# (三)成员函数 类X中每一个非静态成员函数都会接受一个特殊的隐藏参数——this ...

  2. C++类继承内存布局(二)

    转自:http://blog.csdn.net/jiangyi711/article/details/4890889# (二 )成员变量 前面介绍完了类布局,接下来考虑不同的继承方式下,访问成员变量的 ...

  3. 继承虚函数浅谈 c++ 类,继承类,有虚函数的类,虚拟继承的类的内存布局,使用vs2010打印布局结果。

    本文笔者在青岛逛街的时候突然想到的...最近就有想写几篇关于继承虚函数的笔记,所以回家到之后就奋笔疾书的写出来发布了 应用sizeof函数求类巨细这个问题在很多面试,口试题中很轻易考,而涉及到类的时候 ...

  4. [CPP] 类的内存布局

    本文可以解决下面 3 个问题: 以不同方式继承之后,类的成员变量是如何分布的? 虚函数表及虚函数表指针,在可执行文件中的位置? 单一继承.多继承.虚拟继承之后,类的虚函数表的内容是如何变化的? 在这里 ...

  5. cl查看类的内存布局

    查看单个类的内存布局 Microsoft Visual Studio编译器cl的编译选项可以查看源文件中某个C++类的内存布局,对于想了解某个对象的内存布局的人来说十分直观和方便. • 命令格式    ...

  6. c++类的内存布局

    问题: 考察了reinterpret_cast和static_cast的区别.顺道发现了一个可以查看c++内存布局的工具(在VS中). 结果: 前两个输出的地址形同,后一个不同. class A{in ...

  7. VS2010下如何查看类的内存布局

    用VS2010查看类的内存布局,这里用两种方法 (1)MSVC有个隐藏的"/d1"开关,通过这个开关可以查看项目中类的内存布局情况. 修改项目属性,添加"/d1 repo ...

  8. 【C++对象模型】使用gcc、clang和VC++显示C++类的内存布局

    引言 各种C++实现对C++类/对象的内存布局可能有所不同,包括数据成员的顺序.虚函数表(virtual table: vtbl)的结构.继承关系的处理等.了解C++类/对象的布局,对于理解C++各种 ...

  9. c++中如何查看一个类的内存布局

    打开VS command prompt,输入下述命令可以看到对象的内存布局. cl a.cpp -d1 reportSingleClassLayout[classname] //  reportSin ...

随机推荐

  1. Android内存分析工具DDMS heap + MAT 安装和使用

    一  Java内存分析工具扫盲 如果像我一样一点都不了解,可以先进行内存分析工具扫盲   MAT介绍:     Eclipse Memory Analyzer(MAT)一个功能丰富的 JAVA 堆转储 ...

  2. Java实现文件的RSA和DES加密算法

    根据密钥类型不同将现代密码技术分为两类:对称加密算法(秘密钥匙加密)和非对称加密算法(公开密钥加密) 对称钥匙加密系统是加密和解密均采用同一把秘密钥匙,而且通信双方都必须获得这把钥匙,并保持钥匙的秘密 ...

  3. git版本号管理工具的上手

    git是一个分布式的版本号管理工具 和其它集中式版本号管理 工具相比具有下面长处: 1.能够在不联网的情况下开发 2.能够方便的建立本地分支 3.本地化的日志,高速获得信息 git命令的使用 mkdi ...

  4. Android Studio 完美修改应用包名

    我们平时新建项目有些朋友可能当时就是随意写的一个包名,然后在项目过程中, 又感觉这个包名不太好,所以就要对包名进行修改,根据我们正常的修改方式,是这样的. 在种情况是只能修改最外层的那个名称, 如果我 ...

  5. 考试必备神器-真题园手机客户端Android版1.1正式上线啦,欢迎大家下载使用!

    真题园-考试必备神器,爱学习,找真题! 真题园-考试必备神器,爱学习,找真题!1.真题园android客户端1.1版全新发布上线.2.最新教育资讯实时更新,了解行业动态新闻.3.最新最全的真题大全,最 ...

  6. Android开发之自定义圆形的ImageView的实现

    android中的ImageView只能显示矩形的图片,这样一来不能满足我们其他的需求,比如要显示圆形的图片,这个时候,我们就需要自定义ImageView了,其原理就是首先获取到图片的Bitmap,然 ...

  7. 深入理解计算机系统第二版习题解答CSAPP 2.2

    填写空白项. n 2n(十进制) 2n(十六进制) 9 512 0x200 19 0x80000 16384 0x4000 0x10000 17 0x20000 32 0x20 0x80

  8. js 如何判断数据是数据还是对象

    如果用typeof测试,数组和对象都是显示的Object, 测试方式:var mycars=new Array();mycars[0]="Saab";mycars[1]=" ...

  9. 关于IPv6

    App在本地IPv6的测试环境下运行一切正常,结果又是被拒,悲剧原因还是IPv6的问题;求解决方法被拒原因We discovered one or more bugs in your app when ...

  10. 首页TAB页的技术选择与功能实现

    一般的选择是用viewpager,这个有开源库.不过因为要引入开源库,所以有时候显得比较大. 下面这段代码是用在一个BaseActivity上面的,下面继承的Activity直接使用该方法,就可以完成 ...