C语言填坑之结构体对齐
一、什么是字节对齐?
计算机中,内存空间按照字节划分,CPU根据数据总线的位数来确定待读取数据地址起始边界,通常为数据总线长度的倍数,并且数据读取的长度也为数据总线长度的倍数(硬件设计决定)。
因此,如果数据存储按照一定的规则在内存空间上排列,就能保证CPU用尽可能少的访问次数就能获取到完整数据,这就是字节对齐。
二、为什么需要对齐?
(1)空间换时间,提高CPU访问数据的效率:
CPU可以使用最少的次数访问对齐后的数据,如果数据未进行对齐,CPU需要多次访问,并整合结果,才能得到预期数据。
(2)提高代码的可移植性或兼容性:
在某些系统上,如MIPS或者sparc,访问不对齐的数据可能会出现未知问题(segment error)。
比如:
char test[8];
char *p = &test [1];
int i = *(int*)p;
三、如何对齐?
1、基本数据类型对齐
基本原则:自然对齐
自然对齐是指:数据存放的起始地址为对应基础数据类型所占内存大小的的整数倍。
如:double为8字节,则double型数据的起始地址应为8的整数倍;short为2字节,则short型数据的起始地址为2的整数倍。
2、结构体内部对齐
基本原则:偏移对齐与尾部对齐
偏移对齐是指:一个结构体成员相对于结构体起始地址的偏移量所应该进行的对齐。可以理解为,假设结构体的起始地址为0x0,每个成员的起始地址都要求满足自然对齐。当成员不满足偏移对齐原则时,编译器会自动在结构体成员之间进行数据填充。
尾部对齐是指:在结构体的内部成员起始地址满足偏移对齐后,还需要保证结构体的大小为内部成员最大对齐长度的整数倍。如不满足时,编译器会结构体的尾部进行数据填充。
如:
struct test_offset {
char a; // 首地址假设为0x0,天然对齐
char pandding_a[3]; // 为满足b起始地址自然对齐,编译器自动补充3个char型数据
int b; // b起始地址为0x4,int类型长度为4,满足自然对齐;
char c; // c的起始地址为0x8
char pandding_c[7]; // 为满足d的起始地址自然对齐,编译器自动补充7个char型数据
double d; // d的起始地址为0x10,double类型长度为8,满足自然对齐
char e; // e的起始地址为0x18,满足自然对齐
char pandding_e[7]; // 为保证结构体整体大小满足尾部对齐,编译器自动补充7个字节:结构体整体大小为32字节,与内部成员最大对齐长度8成倍数关系
}
3、结构体外部对齐
基本原则:最大长度对齐
结构体外部对齐是指:结构体在内存中的起始地址,需要满足为内部成员最大对齐长度的整数倍。
如2中struct test_offset,其内部成员最大对齐长度为8,则要求test_offset类型变量的的起始地址为8的整数倍。因为只有如此,才能保证最大对齐长度的内部成员(如d)的起始地址满足偏移对齐。将结构体看成一个整体,最大长度对齐原则其实就是自然对齐原则的衍生。
该原则也可以用在结构体嵌套中,需要使内部嵌套的结构体的首地址满足最大长度对齐原则。
如:
struct max_long_test {
char s; // 首地址假设为0x0,天然对齐
char pandding_s[7]; // 为保证内部结构体的首地址满足最大长度对齐,编译器自动补充7个字节
struct test_offset t; // 偏移地址为0x8,内部成员最大对齐长度为0x8,满足最大长度对齐原则
char q;
char pandding_q[7]; // 满足结构体的尾部对齐原则
}
4、结构体中的数组对齐
基本原则:自然对齐
结构体的数组成员,其偏移对齐方式与内存对齐方相同,若对齐后,数组的后续成员之间有间隙,也需要进行填充。当计算一个结构体的最
大对齐值的时候,数组成员的对齐值等于数组元素类型的对齐值。
5、操作步骤
对齐步骤:
第1步:结构体内部,每个成员保证满足偏移对齐原则;
第2步:结构体尾部,利用内部成员最大对齐长度标定结构体整体长度,满足尾部对齐原则;
第3步:结构体外部,结构体的起始地址(包含嵌套时的尾部结构体的起始地址)为内部成员最大对齐长度的整数倍,满足最大对齐原则。
四、指定对齐
(1)指定对齐方式
方式一:pragma指定
#pragma pack (value)
......
// 指定对齐字节后,一定要在对应位置取消指定,否则可能会误影响其他文件的编译;
#pragma () // ()表示没有指定,即配置为CPU默认对齐值
方式二:编译选项
编译参数配置-fpack-struct=value时,指定对齐值value。
方式三:__attribute__选项
_attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
_attribute ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
(2)指定对齐原则
指定对齐后,结构体中成员偏移地址,需要按照指定对齐长度与自然对齐长度中的较小值,进行对齐。
(3)静态变量存放在全局数据区内,而sizeof计算栈中分配的空间的大小
五、参考
[1]https://blog.csdn.net/lanzhihui_10086/article/details/44353381
[2]https://www.zhihu.com/question/27862634