C/C++ 的结构,就像是一个将几种数据结构打包的功能。在使用的时候,可能会注意到,结构体的大小不等于结构体所有成员的大小之和,原因是编译器进行了字节对齐。
字节对齐的目的
如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在 32 位 CPU 下,假设一个整型变量的地址为 0x00000004
(下面简写为 0x04
),那它就是自然对齐的。
需要字节对齐的根本原因在于 CPU 访问数据的效率问题。32 位 CPU 把内存的每 4 个字节分为一组,一次可以同时访问一组。比如,可以同时访问 0x00 - 0x03
,也可以同时访问 0x04 - 0x07
,但不能同时访问 0x03 - 0x06
。
若不进行字节对齐,假如一个 int 存储在了 0x03 - 0x06
,那么读取这个 int 需要 CPU 读取两次内存,降低了效率。而进行了字节对齐,效率会有一定的提升。
这段话也解释了,64 位系统相比 32 位系统更大(由于字节对齐,需要更多的空位),但是运行速度会快一些(对于 8 字节的数据类型,如 long long
和 double
的读取会快一些)。
C 语言编译器对字节对齐的要求
- 标准数据类型:只要地址是它的长度的整数倍就行了;
- 数组:按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
- 联合:按其包含的长度最大的数据类型对齐。
- 结构体:结构体中每个数据类型都要按其包含的长度最大的数据类型对齐。
如
1 | struct foo |
该结构中,c10
虽然只占 10 个字节,但是由于它要对齐 8 字节的 long long,因此会在 c10
后填充到 16 个字节(填充了 6 字节)。同样的, c
后面也会填充到 8 字节。
整个结构占了 16 + 8 + 8 = 32 字节。
把上述代码的 long long 改为 int,整个结构占了 12 + 4 + 4 = 20 字节。
1 | struct foo |
手动设置字节对齐
在设计不同CPU下的通信协议时,或者编写硬件驱动程序时,寄存器的结构这两个地方都需要按统一字节对齐,所以需要手动设定字节对齐。
可以使用 #pragma pack()
语句设置字节对齐,如下:
1 |
|
结构体占了 10 + 4 + 1 = 15 字节。