最近在群里看到不少人在讨论结构体字节对齐的问题,就顺便看了看,总结一下。
在C中,计算一个结构体的大小不是简单地将其各成员所占空间的大小相加,而是有特定的规则 - 字节对齐。使用字节对齐的原因有两个,第一是某些平台只能在特定地址访问特定的数据,二是提高数据存取的速度(尽量减少存取次数)。C99标准并没有明确规定内存字节对齐的细节,具体细则应由编译器决定。
这里首先引入对齐参数这一概念,这是结构体字节对齐的一个参考量,对于每种变量的对齐参数,不同编译器实现不同,这里参考网上列出GCC和MSVC两种编译器对应的对齐参数(均为32位):
编译器 | 对应量 | char | bool(cpp) | short | int | float | double | 指针 |
---|---|---|---|---|---|---|---|---|
VC++2015 | 所占空间 | 1 | 1 | 2 | 4 | 4 | 8 | 4 |
VC++2015 | 对齐参数 | 1 | 1 | 2 | 4 | 4 | 8 | 4 |
gcc 4.8.2(linux) | 所占空间 | 1 | 1 | 2 | 4 | 4 | 8 | 4 |
gcc 4.8.2(linux) | 对齐参数 | 1 | 1 | 2 | 4 | 4 | 4 | 4 |
编译器还可以设置默认对齐参数,用预编译语句#pragma pack(n)
表示,在Windows(32位)下n可取1,2,4,8,默认为8;在Linux(32位)下可取1,2,4,默认为4。
最后,每个变量实际的对齐参数为每个成员变量的对齐参数和编译器的默认对齐参数中较小的一个,比如一个float型变量的对齐参数为4,编译器设定的#pragma pack(8)
,则此变量实际的对齐参数就为4;对于结构体变量,它的自身对齐参数就为它里面各个变量最终对齐参数的最大值。
结构体字节对齐的原则主要有两点:
- 结构体的每一个成员相对结构体首地址的偏移量应该是对其参数的整数倍,如果不满足则补足前面的字节使其满足
- 结构体的最终大小应该是其对应参数的整数倍
下面用例子来说明(C++):
node1
为一个空结构体,在C中空结构体的大小为0字节,在C++中空结构体的大小为1字节。node2
的内存结构:(4 — 1 — 1(补) — 2),总大小为8字节。node3
的内存结构:(1 — 3(补) — 4 — 4),总大小为12字节。node4
的内存结构:(4 — 2 — 2(补)),总大小为8字节,注意静态变量被分配到静态数据区,不在sizeof计算的范围内。node5
的内存结构:(1 — 1 — 2),总大小为4字节。node6
的内存结构:(1 — 3(补) — 8 — 4),总大小为16字节,注意结构体变量的对齐参数的计算。node7
的内存结构:(1 — 3(补)— 8 — 4(补) — 8 — 4 — 4(补)),总大小为32字节。node8
的内存结构:(1 — 3(补) — 8 — 4),总大小为16字节。
详细分析一下node7
,其余的也类似:
#pragma pack(n)
为8- 对于
a
变量,其对齐参数为1,此时offset=0,可以被1整除,因此为其分配1字节空间; - 对于
b
变量,其对齐参数为4(s2结构体的成员变量最大对齐参数为int => 4),此时offset=1,不能被4整除,因此填充3字节后为其分配8字节空间; - 对于
d
变量,其对齐参数为8,此时offset=12,不能被8整除,因此填充4字节后为其分配8字节空间。 - 对于
c
变量,其对齐参数为4,此时offset=24,可以被4整除,因此为其分配4字节空间。 - 此时所有变量都分配完,但此时offset=28,不能被最大对齐参数8整除,因此填充4字节使其可以被8整除。所以最后node7的大小为32字节。