转自: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. HW4.12

    public class Solution { public static void main(String[] args) { int n = 0; while(n * n < 12000) ...

  2. VC++深入详解-第一章学习心得(一)

    句柄是系统为资源分配内存的标识号. 相当于一个指针指向一块内存空间,我暂时理解成一个地址,一个门牌号 HWND 窗口句柄 HICON 图标句柄 HCURSOR 光标句柄 HBRUSH 画刷句柄 消息的 ...

  3. github上值得关注的前端项目

    https://segmentfault.com/a/1190000002804472

  4. C#中只使用Invokerequired来判断是不是UI线程可靠吗?

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:C#中只使用Invokerequired来判断是不是UI线程可靠吗?.

  5. JDK之jstat的用法

    http://www.51testing.com/html/92/77492-203728.html jstat的用法 用以判断JVM是否存在内存问题呢?如何判断JVM垃圾回收是否正常?一般的top指 ...

  6. Dragons

    http://codeforces.com/problemset/problem/230/A Dragons time limit per test 2 seconds memory limit pe ...

  7. C#数据类型中的decimal精度比double更高

    decimal 128bit大小 有效数字:28~29,虽然decimal类型有比浮点类型更高的精度,但它的范围更小.故double转decimal有可能发生溢出错误,此外,decimal的计算速度稍 ...

  8. RxAndroid结合Retrofit,看看谁才是最佳拍档!

    这篇博文酝酿好久了,今天终于下定决心开始写!RxAndroid和Retrofit都算是当下非常流行的Android开发框架,这两个框架光是单独使用就已经爽歪歪了,那么将RxAndroid和Retrof ...

  9. Java并发——显示锁

    Java提供一系列的显示锁类,均位于java.util.concurrent.locks包中. 锁的分类: 排他锁,共享锁 排他锁又被称为独占锁,即读写互斥.写写互斥.读读互斥. Java的ReadW ...

  10. 【转载】NIO服务端序列图

    步骤一:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道,代码示例如下: ServerSocketChannel acceptorSvr = ServerS ...