c++类的内存布局

原文地址:点击打开链接

本文基本上是对于Stanley B.Lippman的Inside The C++ Object Model一书第一章第三章的概括,描述了c++类的内存布局情况.

c++的类的内存布局有如下规则:
1. Nonstatic data member 存放在Class Object中;
2. Static data member, static/nonstatic member function存放在class object之外.
3. 若类有virtural function, 则在class object 中增加virtual pointer(vptr)指向virtural function tabel(vtbl). vptr在类中的位置有两种情况:1是在所有成员变量之后,这么做的好处是这个类能和c语言兼容.2是在最前面,这样做的话,虚拟继承等实现会方便一点,但是不能和c兼容.在前或后依赖编译器实现.
4. nonstatic data member 若在同一个access section中, 则变量在内存中的顺序保证和声明中的顺序一致(较晚出现的变量有较高的地址).而不同access section中的数据顺序则没有保证,依赖编译器实现.
5. 内存对齐: 类的各个成员,第一个成员位于offset为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack(), 这个数据成员自身的长度)的倍数
             数据成员自身对齐后, 类本身也要进行对齐, 对齐将按照min(#pragma pack(), 类中长度最大的成员)的倍数进行.
6. 如果类为空,编译器会安插1byte的数据到类中,以确保类的每个实例都会有唯一的内存地址
7. 继承后,子类的数据成员不会占用父类内存对齐用的空间. C++语言保证:"出现在derivd class 中的base class subobject有其完整的原样性"
8. 如果类的继承体系不是单一,而是多重继承,但是不含虚拟继承,那么有多少条继承链,内存布局中就有多少个vptr.多个继承链的位置,和继承时的声明顺序一致.
9. 如果使用了虚拟继承,则先将derived class的不变部分布局,然后再布局虚拟继承的base class,而具体布局则有以下情况:
  1. 使用Pointer Strategy, 每一个虚拟继承的类,都有一个额外的指针指向base class
  2. 使用Virtual table offset strategy, 不加入额外的指针指向base class,而是在vtbl的-1的offset内放置该类与虚拟继承的基类之间的offset. 这样的话运行时则可以通过derivedPointer + vptr[-1]得到.
  3. 使用Virtual base class table. 这个是微软的做法,不过书中并没有具体描述怎么做,根据我的理解好像是在vtbl中加入一个指针指向base class.
 如果采用2或3, 那么内存布局和8并不会有太大区别,就是virtual base class跑到最后面去了.
 第9种情况异常复杂,建议看原书外加自己在多个编译器上实践实践.
 
用实例来说说(32位机器)
规则1和2:
class A
{
public:
 int a;
 static char b;
 void foo();
 static void bar();
};
ASSERT(sizeof(A) == 4);
那么在每一个A对象内,只含有一个a,也就是,sizeof(A) == 4 .
而b,foo(),bar(),都不在class object之内,他们在内存中有唯一实体.

规则3
class B
{
public:
 int a;
 virtual void foo();
};
ASSERT(sizeof(B) == 8);
一个int和一个vptr,共8位

规则4,没啥好说的,一般就算在不同的access section,都会按照一致的顺序来声明.但是要注意顺序一致不代表连续.因为变量间可能会有一些bytes用于内存对齐.

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

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

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

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

规则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);
4个int一共是16,BHI3个类都有各自的vptr,16+4*3==28.

这东西其实我一年半前就看书看到过,可惜实际编程中基本是用不到这种东西的,导致我忘了不少,面试的时候有几个没答出来,十分可惜,特意花一天时间重新啃了那书再总结总结加深记忆.另外真的要对自己说声加油啊.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值