本节摘要:结构体成员按声明顺序排布,但每个成员的偏移必须是"自身对齐值"的倍数,编译器因此在成员之间插入填充字节;结构体总大小还要补齐到最大成员对齐值的倍数。这两条规则解释了 sizeof 的所有"意外"。本节手推一个结构体的完整布局,用 offsetof 核对,再讲压缩技巧与跨平台注意。
#include <stdio.h> struct Bad { char a; /* 1 字节 */ int b; /* 4 字节 */ char c; /* 1 字节 */ }; struct Good { int b; char a; char c; }; int main(void) { printf("Bad %zu\n", sizeof(struct Bad)); /* 12 */ printf("Good %zu\n", sizeof(struct Good)); /* 8 */ return 0; }
成员一样、顺序不同,大小差 50%。答案藏在填充字节里。
规则一(成员对齐):每个成员的偏移量必须是"该成员对齐值"的倍数。对齐值通常等于成员大小(char 为 1、short 为 2、int 为 4、double 为 8、指针为 8)。不满足就在前面垫填充。
规则二(结构体整体对齐):结构体总大小必须是"最大成员对齐值"的倍数,不足在尾部补齐——保证数组里下一个元素也满足规则一。
逐字节排 struct Bad:
偏移 0 : a (char, 1 字节) 偏移 1 : 填充 (3 字节) —— b 需要 4 的倍数偏移 偏移 4 : b (int, 4 字节) 偏移 8 : c (char, 1 字节) 偏移 9 : 填充 (3 字节) —— 总大小补齐到 4 的倍数 合计 12 字节,其中 6 字节是填充
struct Good 把大成员放前面:b 占 0-3,a 占 4,c 占 5,尾部补 2,共 8 字节——填充从 6 降到 2。排座位原则:大成员在前,小成员靠后聚拢。

标准头里提供了验证工具:
#include <stdio.h> #include <stddef.h> int main(void) { printf("a 偏移 %zu\n", offsetof(struct Bad, a)); /* 0 */ printf("b 偏移 %zu\n", offsetof(struct Bad, b)); /* 4 */ printf("c 偏移 %zu\n", offsetof(struct Bad, c)); /* 8 */ return 0; }
offsetof 的实现思路本身就是宏加指针运算的绝妙演出:((size_t)&((T*)0)->member)——把空指针强转成结构体指针再取成员地址,地址值恰是偏移量(从不真的解引用,零地址只做标尺)。第 5 章"指针只是地址运算"的又一块拼图。
何时在意填充:百万级元素的数组里每个省 4 字节就是 4MB;嵌入式设备内存按 KB 计较——排座位省下的都是真金白银。桌面小对象则不必过度优化,可读性优先。
绝不能直接把结构体内存当字节流发出去。填充字节内容不确定、各平台对齐规则不同、字节序不同——两台机器直接内存转储结构体通信,收到的是天书。正确做法是逐字段序列化成明确格式的字节流。同理,fwrite(&stu, sizeof stu, ...) 整块写结构体文件,读回方必须完全相同的编译器与平台——跨平台数据交换禁用。
强制紧凑布局:需要按精确布局对接硬件或协议时,可让编译器紧凑排布(各编译器提供 pack 指令,紧凑到 1 字节对齐)。代价是未对齐访问可能变慢或崩溃,x86 宽容、某些 ARM 严格。用前先确认目标平台。
结构体赋值与传参:结构体可以整体赋值(编译器生成逐块复制,与 memcpy 等价),这是数组做不到的(数组名退化)。传参时大结构体建议传指针——第 4 章调用约定讲过,传值整块复制,传指针 8 字节。
⚠️ 常见坑:以为把 char 挪到后面就万事大吉。若结构体里有 double(对齐 8),头尾都要按 8 补;修改成员后 sizeof 变了,别把 sizeof 的旧值写死在代码或磁盘格式里。
💡 关键直觉:声明结构体成员时想一句"从大到小排队,零碎的聚在队尾"。一行排序换一半填充,性价比极高的习惯。
下一节看结构体的两个近亲:共用体的内存共享,与枚举、typedef 的命名之道。
问:对齐值永远等于成员大小吗? 主流平台上基本如此,但标准允许更大(例如某些平台对 double 要求更强对齐)。需要精确控制时用 alignof 查询成员的对齐值,别靠猜。
问:把结构体改成紧凑布局就更好吗? 不一定。紧凑布局省内存但可能产生未对齐访问,在宽容的 x86 上变慢、在严格的架构上直接崩溃。省内存与保性能二选一时,先确认目标平台行为。
问:嵌套结构体的对齐怎么算? 把内层结构体的最大对齐值当作一个"虚拟成员"的对齐值参与规则一与规则二即可。层层套用两条规则,任何嵌套布局都能手推出来。