先介绍下几个概念:
自身对齐值:数据类型本身的对齐值,例如char类型的自身对齐值是1,short类型是2;
结构体或类的自身对齐值:其成员中自身对齐值最大的那个值;
指定对齐值:编译器或程序员指定的对齐值;
有效对齐值:自身对齐值和指定对齐值中较小的那个,我们最终是以这个值进行对齐的。
为什么要对齐:
简单的说就是为了提高访问效率。计算机在存取数据的时候,因为我们的数据在内存中占用的大小是不一样的,假设现在我们将一个char(一个字节)数据和一个double(8个字节)的数据读出,而一次只能读出4个字节,这样我们要读三次才能将所有数据读出,如图所示,
我们读取这两个数据读了三次,而且这个double型数据会被切成了三段,读完之后,我们还需要进行拼接,这样读取一个数据就变得复杂了,效率就变低了。
如果我们按4字节对齐,就如下图所示,这样我们的double数据只需要两次就能读出,且拼接也简单得多。
到底怎么对齐呢?
首先我们要知道以几字节对齐,即我们的有效对齐值是几。以几字节对齐,可以理解为就是一次可以放几字节的数据,如上图,以4字节对齐,就是一次可以放四个字节,然后我们再把我们的成员按顺序一个个放进去,放不进去,就放到下一次,可以空余,不可溢出。如果我们的数据比我们的对齐值还大怎么办呢?这时我们需要把它分割成与有效对齐值一样大再放进去,要是分不匀怎么办?可以告诉你这种情况是不存在的,因为我们只能以1、2、4、8、16对齐(笔者实验到32就报错了),我们的基本数据类型的大小也是1、2、4、8,所以不存在分不匀。
下面我们用代码实验下:
#include<stdio.h>
//#pragma pack(4)
void main(void){
struct s1{
char a;
char b;
char c;
}s1;
struct s2{
char a;
short b;
char c;
}s2;
struct s3{
char a;
int b[8];
char c;
}s3;
struct s4{
char a;
double b;
char c;
}s4;
struct s5{
char a;
short b;
struct s4 c;
}s5;
printf("s1:%d \n",(int)sizeof(s1));
printf("s2:%d \n",(int)sizeof(s2));
printf("s3:%d \n",(int)sizeof(s3));
printf("s4:%d \n",(int)sizeof(s4));
printf("s5:%d \n",(int)sizeof(s5));
}
执行结果:
s1:3
s2:6
s3:40
s4:24
s5:32
分析:
s1:自身对齐值为1,有效对齐值也是1,所以大小为1+1+1=3
s2:自身对齐值为2,有效对齐值也是2,所以大小为2+2+2=6
s3:数组b可以看做是8个int型数,所以自身对齐值为4,大小为4+4X8+4=40
s4:自身对齐值为8,有效对齐值也是8,所以大小为8+8+8=24
s5:这里面还有一个结构体s4,找对齐值时需要看s4里面的成员,而不是整个大小,s4成员最大的为8,s5其他成员最大为2,所以s5的自身对齐值为8,有效对齐值也是8,另外需要注意的s4成员不能补到s5的其他成员后面,所以s5的大小为8+(8+8+8)=32
看完上面的分析也许你会有疑问,为什么自身对齐值都和有效对齐值一样呢,那指定对齐值是多少呢?在GCC中是没有默认指定值的,所以有效对齐值就和自身对齐值一样了,我们将指定对齐值设为4,再实验一次,将上面代码的//#pragma pack(4)的屏蔽去掉即可。
执行结果:
s1:3
s2:6
s3:40
s4:16
s5:20
分析:
s1:自身对齐值为1,指定对齐值是4,有效对齐值,是1,所以大小为1+1+1=3
s2:自身对齐值为2,指定对齐值是4,有效对齐值,是2,所以大小为2+2+2=6
s3:自身对齐值为4,指定对齐值是4,有效对齐值,是4,所以大小为4+4X8+4=40
s4:自身对齐值为8,指定对齐值是4,有效对齐值,是4,所以大小为4+(4+4)+4=16
s5:自身对齐值为8,指定对齐值是4,有效对齐值,是4,所以大小为4+(4+(4+4)+4)=20
你会发现我们再进行结构体字节对齐时,空了很多内存,所以结构体对齐是一种空间换时间的做法