结构体内存对齐——你需要知道的都在这里
提出问题
- 首先,要申明的是,我们声明的结构体类型或者类自身是没有空间的,这只是一种数据类型的集合,是一种数据组织形式
- 而当创建了结构体对象(实例化),才有真正的空间存在
- 如果看到使用sizeof(struct A),其实代表的是这个结构体/类实例化以后的大小
- 看下面这段代码,你能计算得到正确的输出吗?
struct A{
char c;
int a;
};
class B{
char c;
int a;
};
int main(){
cout<<sizeof(struct A)<<endl; //输出8
cout<<sizeof(class B)<<endl; //输出8
return 0;
}
- 看上面这个例子,很多人可能会认为答案是1+4=5Bytes,其实结果是8,这就是我们今天要学习的内容,请接着往下看
为什么需要内存对齐?
- 这和取内存的操作有关,一般存取内存是按组来存取,如果需要取的数据跨越了两组,那么取数据时效率就会变低
- 所以结构体对齐是为了更快的内存读取,本质上也是用空间换时间的手段之一
结构体(类)大小的计算
- 拿到一个结构体,结构体里或许会有数组,有整型(int),浮点型(float、double),字符型(char)等等,甚至有可能有另一个结构体
- 拿到一个类,类内会有成员变量与成员函数,或者其他类,那么要记住的是:只有非静态成员变量才属于这个实例的空间上,虚函数会存在指向虚函数表的指针,其余都不在这个实例化的对象上
第一个概念:最小对齐单位
- 在windows环境下,一般系统默认对齐单位为8,而在linux系统里是区分32位或64位系统的,32位默认对齐单位为4,64位系统默认为8
- 你也可以通过自定义来设置对齐单位,通过#pragma pack(n)来设置
- 取决于结构体中最大的那个数据类型所占用的大小,一般也就是double的8位了
- 在根据上述的三条得出对齐单位后,选出最小的一个就是这个结构体的最小对齐单位。
- 在清楚最小对齐单位后,你需要知道的事情是,这整个结构体,最后的总大小,一定是这个最小对齐单位的整数倍,例如最小对齐单位为8,则整个结构体的空间就是8的倍数。
第二个概念:结构体中成员的摆放规则
- 现在我们要将结构体中的成员放入内存空间,肯定不是挨着放或者随便放那么简单,而是要遵循结构体内存对齐的原则
- 当你遇到一个成员时,你应该首先考虑其类型所占的字节数,例如char类型就考虑占1个字节,int类型就考虑占4个字节,然后把这个成员变量填在其类型所占字节数整数倍的地方;假如这个成员是另一个结构体:例如结构体B内嵌套着1个结构体A,那么这个结构体A在结构体B的空间中应该从哪里开始摆呢?答案是:从这个A结构体中最大占用字节数的整数倍开始摆!!例如:结构体A中有char、int、double,则A结构体在B中就要从double(8字节)的整数倍开始摆。
- 最后,之前说过整个结构体大小一定是最小对齐单位的整数倍,所以末尾不足要补齐
接下来就是实践环节:
我们就举一两个稍微复杂的例子吧
struct A
{
char c1;
int n;
char c2;
};
struct B
{
char c1;
A a;
double b;
char c2;
};
请问B结构体所占大小为多少?答案是32哦!
详细解析
struct A //最小对齐单位为4,因为int类型占4Bytes
{
char c1; //本来是1,但因为下面的int要从4的整数倍开始摆,所以+3B的冗余
int n; //正常为4
char c2; //本来是1,但因为最终结构体大小需要时最小对齐单位的整数倍,故升级为4,有3B的冗余
}; //所以答案就是4+4+4=12
struct B //最小对齐单位是8
{
char c1; //本来占一个字节,但下面的a要从a的最小对齐单位4开始,所以+3B冗余 4
A a; //上面已经算出来了,占12字节 12
double b; //刚好从double也就是8的整数倍开始,所以不用挪,就是8Bytes 8
char c2; //一个字节,但为了整体结构体大小要是最小对齐单位的整数倍,故+7Bytes冗余 8
}; //所以答案就是4+12+8+8=32