对象的内存布局
在class和struct中,编译器不会把数据程序员紧凑的包裹在一起,因为每种数据都有其天然的对齐方式,供CPU高效的从内存读/写。对齐方式即内存地址为对齐字节大小的倍数,另外编译器可能会为了数组的对齐,会在末端加入填充,以下是32位系统下各个类型的大小(单位:字节):
int:4字节;float:4字节;double:8字节;bool:1字节;char:1字节;short:2字节;long:4字节或者8字节
例如下图:
struct InefficientPacking
{
U32 mU1; //32位
F32 mF2; //32位
U8 mB3; //8位
I32 mI4; //32位
bool mB5; //8位
char* mP6; //32位
};
现在,我们重新考虑上图的中struct InefficientPacking布局里的空隙。在class或struct中,当把较小的数据类型(如8位的bool)放置于较大类型(如32位的float)之间,编译器会加入填充(空隙),以保证所有成员都是正常地对齐的。当声明数据结构时,认真对待对齐和包裹是个好习惯。如以下代码及图所示,只需简单地重新排列上述例子中的成员,就能省去了一些浪费了的填充空间。
struct MoreEfficientPacking
{
U32 mU1; //32位(4字节对齐)
F32 mF2; //32位(4字节对齐)
I32 mI4; //32位(4字节对齐)
char* mP6; //32位(4字节对齐)
U8 mB3; //8位(1字节对齐)
bool mB5; //8位(1字节对齐)
};
在内存布局上,C++的类有别于C的结构之处有二——继承与虚函数。
当B类继承自A类,内存里B类的数据成员会紧接A类数据成员之后,如图所示。
需要说明的是,当class中有虚函数的时候,或者是继承的类中有虚函数的时候,通常会在类的布局最前端加入一个虚表指针,它指向名为虚函数表的一个数据结构,因为指针是int类型的。
了解内存布局的意义是,当我们写类和结构体的时候,最优化的处理方式是自己按照内存布局规则把数据排列好,从而可以降低类或者结构体所占的大小。