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

posted @ 2022-10-26 23:47  Pangolin2  阅读(533)  评论(0编辑  收藏  举报