63. 字节对齐:#pragma pack 的作用
说到字节对齐,我估计不少朋友都吃过它的亏。
结构体里明明只定义了几个小变量,用 sizeof 一量,结果比预想的大一圈。我当时刚入行时也纳闷过——这多出来的空间到底哪来的?
嗯,今天咱们就把 #pragma pack 这个“对齐开关”彻底讲明白。
什么是字节对齐?
说白了,CPU 读取内存不是按字节一个一个读的。它更喜欢按“块”来读——比如 4 字节一块、8 字节一块。如果数据刚好落在这个“块”的边界上,一次就读完了。如果跨了边界,就得读两次,甚至还要拼数据。
编译器为了性能,会自动在结构体成员之间插入一些“填充字节”,让每个成员都对齐到它自然边界上。这就是默认的字节对齐。
比如
int 占 4 字节,它的地址最好是 4 的倍数。
默认对齐的“坑”
看个例子:
struct Test {
char a; // 1 字节
int b; // 4 字节
short c; // 2 字节
};
你觉得 sizeof(struct Test) 是多少?
直觉上:1 + 4 + 2 = 7 字节。但实际跑一下,大概率是 12。
为什么会这样?
a占 1 字节,地址 0b是 int,需要 4 字节对齐。所以它不能紧跟在 a 后面(地址 1 不是 4 的倍数),编译器会在 a 后面填 3 个字节,让 b 从地址 4 开始c是 short,2 字节对齐,从地址 8 开始,占 2 字节- 最后,整个结构体的大小必须是最大成员(int,4 字节)的整数倍。目前用了 10 字节,补到 12
你看,白白浪费了 5 个字节。这在嵌入式系统里可是大事——RAM 本来就金贵。
#pragma pack 登场
#pragma pack 就是用来“收紧”对齐规则的。它告诉编译器:别按默认的那么奢侈了,按我指定的字节数来对齐。
#pragma pack(1) // 按 1 字节对齐,也就是“紧凑模式”
struct Test {
char a;
int b;
short c;
};
#pragma pack() // 恢复默认对齐
加了 pack(1) 之后,sizeof(struct Test) 就变成 7 了。成员之间没有任何填充,一个紧挨着一个。
#pragma pack(1)。否则两边对齐方式不一样,数据解析出来全是乱的。
pack 的常用值
| pack 值 | 对齐规则 | 典型场景 |
|---|---|---|
| 1 | 所有成员按 1 字节对齐,无填充 | 网络协议、文件格式、硬件寄存器 |
| 2 | 按 2 字节对齐,short/int 可能被压缩 | 某些 DSP 或 16 位 MCU |
| 4 | 按 4 字节对齐,接近默认行为 | ARM Cortex-M 系列常用 |
| 8 | 按 8 字节对齐,64 位系统默认 | x86-64、ARMv8 等 |
pack 的副作用
凡事都有代价。紧凑对齐省了空间,但可能牺牲性能。
比如一个 int 被放在奇数地址上,CPU 要读两次才能拿到完整数据。在性能敏感的循环里,这种惩罚是肉眼可见的。
pack(1) 之后去访问一个 int 指针,直接触发硬件异常(HardFault)。我曾经在 STM32 上踩过这个坑,排查了半天才发现是结构体对齐搞的鬼。
实战建议
- 通信协议: 必须用
pack(1),保证双方结构体布局一致 - 存储结构: 如果结构体要写入 Flash 或文件,建议用
pack(1),避免版本升级后对齐方式变化导致数据不兼容 - 性能敏感: 尽量别用
pack(1),或者手动把成员按大小从大到小排列,减少填充 - 跨平台: 不同编译器对
#pragma pack的支持略有差异,移植时注意测试
知识体系图
下面这张图帮你理清字节对齐的核心逻辑:
避坑指南
我曾经在做一个 Bootloader 升级协议时,结构体里定义了一个 4 字节的 CRC 字段。本地测试一切正常,发到另一款芯片上就 CRC 校验失败。查了两天才发现——对方编译器默认是 4 字节对齐,而我这边用了 pack(1),两边结构体布局差了 3 个字节。
从那以后,我养成了一个习惯:只要结构体跨模块、跨芯片、跨语言传递,一定在定义处显式声明对齐方式,并且用静态断言检查 sizeof。
// 推荐做法
#pragma pack(1)
typedef struct {
uint8_t type;
uint32_t length;
uint8_t data[64];
uint32_t crc;
} Packet;
#pragma pack()
// 编译时检查
_Static_assert(sizeof(Packet) == 73, "Packet size mismatch!");
嗯,这样就算以后有人改了对齐方式,编译阶段就能发现问题,不用等到联调时抓狂。
字节对齐这事,说大不大,说小不小。但一旦出问题,往往就是那种“看起来都对,跑起来就错”的疑难杂症。希望今天的内容能帮你少走一些弯路。
公众号:蓝海资料掘金营,微信deep3321