一、预热
在讨论Data Member布局之前,先看比较常见的笔试题目:
1、 空class
class X{};
sizeof(X) : 1。空的class中没有任何data member,大小不应该是0吗?大小之所以为1,是因为编译器安插进去一个char,所以空class实际上隐藏1字节的大小。
2、没有virtual 成员函数的class
class Point3d
{
public:
// ...
private:
float x, y, z;
};
sizeof(Point3d) : 4 + 4 + 4 = 12。float占用4个字节,该类定义了三个float类型的data member,大小为12毫无悬念。
3、含有virtual成员函数的class
class Point3d
{
public:
virtual void vir_func() { }
private:
float x, y, z;
};
sizeof(Point3d) : 12 + 4 = 16。其中的4个字节是怎么回事?这一点需要特别注意,编译器还可能会合成一些内部使用的data members,以支持整个对象模型。vptr就是这样的东西,目前所有的编译器都会把它(指向虚表的指针(指针占4个字节)vptr)安插在“内含virtual function之class”的object中。
4、比较混合的class
class Point3d
{
public:
virtual void vir_func() {}
virtual void vir_func2() {}
void func_test() { }
private:
float x;
static list<Point3d*> *freeList;
float y;
static const int chunkSize = 250;
float z;
};
sizeof(Point3d) : 12 + 4 = 16。哦?class Point3d含有两个virtual functions,为什么没有两个4字节被计算呢?傻啊,“指向虚表的指针vptr”在class object中只有一个,而虚表存放的是virtual functions的地址。哦?为什么没有计算static data members?又犯傻了,static member存放在data segment,不存放于class object,当然计算class占用内存大小不需要考虑进去。另外,如果想知道为什么nonstatic、non-virtual function members没有占用内存,狠戳这里。
除了上面阐述的简单模型外,alignment(对齐)不可忽略,需牢记在心。下面正式讨论Data Member布局。
二、Data Member布局
在预热这一小节中,其实讨论了大部分class data members在内存中的存储,这里简单介绍,data member在内存中是如何排列(即布局)的。以下面的class为例:
class Point3d
{
public:
// ...
private:
float x;
static list<Point3d*> *freeList;
float y;
static const int chunkSize = 250;
float z;
};
经过第一小节的分析,该Point3d class object布局中仅有x,y和z。C++Standard要求,同一access section(也就是private、public、protected等区段)中,members的排列只需符合“较晚出现的members在class object中有较高的地址”这一条件即可。也就是说,各个members并不一定连续排列。什么东西可能会介于被声明的members之间呢?members的边界调整(alignment)可能就需要填补一些bytes,也可能是编译器合成的东西(比如:vptr),vptr传统上被放在所有显式声明的members的最后,不过如今也有一些编译器把vptr放在一个class object的最前端。
参考文献:《深度探索C++对象模型》侯捷 译