c++的类的内存布局

转自:http://blog.sina.com.cn/s/blog_b32586610101btqv.html

c++内存布局规则如下:


1. Nonstatic data member 存放在Class Object;
2. Static data member, static/nonstatic member function存放在class object之外.

//

所有实例运行于32位机器上测试

/

· 规则12:            
class A
{
public:
 int a;
 static char b;
 void foo();
 static void bar();
};
ASSERT(sizeof(A) == 4);
那么在每一个A对象内,只含有一个a,也就是,sizeof(A) == .
b,foo(),bar(),都不在class object之内,他们在内存中有唯一实体.

· 
3. 若类有virtural function, 则在class object 中增加virtual pointer(vptr)指向virtural function tabel(vtbl). vptr在类中的位置有两种情况:1是在所有成员变量之后,这么做的好处是这个类能和c语言兼容.2是在最前面,这样做的话,虚拟继承等实现会方便一点,但是不能和c兼容.在前或后依赖编译器实现.

· 规则3
class B
{
public:
 int a;
 virtual void foo();
};
ASSERT(sizeof(B) == 8);
一个int和一个vptr,8
4. nonstatic data member 若在同一个access section{存取区段,publicprivateprotected三种段落}则变量在内存中的顺序保证和声明中的顺序一致(较晚出现的变量有较高的地址).而不同access section中的数据顺序则没有保证,依赖编译器实现.

· 规则4,没啥好说的,一般就算在不同的access section,都会按照一致的顺序来声明.但是要注意顺序一致不代表连续.因为变量间可能会有一些bytes用于内存对齐.
5. 内存对齐类的各个成员,第一个成员位于offset{偏移位置}0的位置,以后每个数据成员的偏移量必须是min(#pragma pack(), 这个数据成员自身的长度)的倍数数据成员自身对齐后类本身也要进行对齐对齐将按照min(#pragma pack(), 类中长度最大的成员)的倍数进行.

· 规则5
class C
{
 int a;
 char b;
};
ASSERT(sizeof(C) == 8);
规则5有两条子规则,第一条对这个例子没用,经过第一条后C的大小还是5,可是第二条要求整个类要对齐,那么必须在char b后增加3bytes.
如果intchar的声明顺序反一下,那么为满足第一条规则,类已经需要对齐成8,已经是8那么第二条也满足了.

· 
6. 如果类为空,编译器会安插1byte的数据到类中,以确保类的每个实例都会有唯一的内存地址

· 规则6
class D
{};
ASSERT(sizeof(D) == 1);
1byte是编译器插进去的,如果不插的话,连续声明a,b;再取他们的地址,就会变成一样的了.就无法分辨哪个变量是哪个了.
不过要注意的是,任何类继承了D,只要里面有vptr或者任何一个变量,那么编译器就不会在子类中加入这byte.(这个是依赖于编译器的,而不是标准规定.如果编译器没有去掉这1byte的话,那么就要内存对齐了.)

· 
7. 继承后,子类的数据成员不会占用父类内存对齐用的空间C++语言保证:"出现在derivd class{衍生类} 中的base class subobject{基类子对象}有其完整的原样性"

· 规则7

· class C
{
 int a;
 char b;
};

class E:public C
{
 int c;
 char d[2];
};
ASSERT(sizeof(E) == 16);
编译器不会为了节省空间把E的成员插入到C为了内存对齐的而补出的空间中的.这道题我面试的时候被问过,我答16的时候面试官还认为错了,太浪费空间了.但是这的确是唯一的正确解.

· 
8. 如果类的继承体系不是单一,而是多重继承,但是不含虚拟继承,那么有多少条继承链,内存布局中就有多少个vptr.多个继承链的位置,和继承时的声明顺序一致.

· 规则8

· class B
{
public:
 int a;
 virtual void foo();
};

class F
{
 int b;
 virtual void bar();
};
class G:public B, public F
{
 int c;
};
ASSERT(sizeof(G) == 20);
3int,2vptr,一共20

· 
9. 如果使用了虚拟继承,则先将derived class的不变部分布局,然后再布局虚拟继承的base class,而具体布局则有以下情况:
  1. 使用Pointer Strategy, 每一个虚拟继承的类,都有一个额外的指针指向base class
  2. 使用Virtual table offset strategy, 不加入额外的指针指向base class,而是在vtbl-1offset内放置该类与虚拟继承的基类之间的offset. 这样的话运行时则可以通过derivedPointer vptr[-1]得到.
  3. 使用Virtual base class table. 这个是微软的做法,不过书中并没有具体描述怎么做,根据我的理解好像是在vtbl中加入一个指针指向base class.
 如果采用23, 那么内存布局和8并不会有太大区别,就是virtual base class跑到最后面去了.
 9种情况异常复杂,建议看原书外加自己在多个编译器上实践实践.

规则9
class H:virtual public B
{
int a;
};
class I:virtual public B
{
int b;
};
class J:public H, public I
{
int a;
};
ASSERT(sizeof(J) == 28);
4int一共是16,BHI3个类都有各自的vptr,16+4*3==28.

 

 

 

1.没有虚函数的单继承

      1、成员变量根据其继承和声明顺序依次放在后面

   2、成员函数定义在类体之外

   3、调整字节对齐使bus运输量达到最高效率


2. 没有虚函数的多重继承

在没有虚函数的情况下,多重继承与单继承没有多大区别, 父类和父类的成员变量根据其继承和声明顺序依次存放后面,子类的成员变量放在最末尾。


3.有虚函数的单继承


1、虚函数表在类的最开始位置

 2、子类覆盖父类的虚函数在虚函数表得到体现

 3、子类新增的虚函数添加到虚函数表的末尾

4.有虚函数的多继承


1、在子类中的每一个父类都有一个虚函数表

  2、子类新增的虚函数,按继承顺序放在第一个父类的虚函数表中

  3、子类覆盖父类的虚函数在每一个父类的虚函数表得到体现

4.有虚函数的重复多继承


1、孩子类存在父类的两份拷贝。

  2、孩子类的第二个父类的虚函数表,有些虚函数条目直接跳转到第一个父类的虚函数表的同名函数的,这只是VC的实现方式而已,其他编译器可能不是这样。

5.有虚函数的虚拟继承

1、虚基类放在孩子类的末尾

  2VC实现虚基类的方式是每一个子类用一个指向虚基类表的指针vbptr,同时,子类的虚函数表不在包含父类的虚函数地址。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值