C 语言结构体布局与对齐深入
更新时间:2026-08-26。本文是
languages/c/主题专家层文档。高手层 结构体内存对齐 讲了"对齐规则和字段重排的用法",本文钻到为什么——CPU 为什么要求对齐?padding 如何影响缓存命中?伪共享又是怎么拖慢多线程的?用实测数据说话。
本文要回答的问题
- 为什么 CPU 访问"非对齐"数据会慢?对齐是硬件强制的吗?
- 同样的字段,换个顺序
sizeof从 24 变 16,差在哪? - 多线程里"两个变量贴太近"为什么会让程序变慢几十倍?
一、对齐的硬件真相
1.1 为什么 CPU 要求对齐
CPU 以"字"为单位访存,一次读 4 字节(或 8 字节)必须落在自然边界上:

对齐访问一次内存事务完成;非对齐访问跨越两个对齐单元,CPU 要读两次再拼接,慢且在某些架构(老 ARM)上直接崩溃。所以编译器在字段间插 padding,保证每个字段落在自然对齐地址。
1.2 对齐规则
| 字段类型 | 对齐(alignof) | 说明 |
|---|---|---|
char | 1 | 无 padding |
short | 2 | 2 字节对齐 |
int / float | 4 | 4 字节对齐 |
double / 指针 | 8 | 8 字节对齐 |
结构体自身的对齐 = 最大字段的对齐(如含 double 则整体 8 字节对齐)。
二、实测:字段顺序决定体积
struct C { char c1; double d; char c2; }; // 乱序
struct D { double d; char c1; char c2; }; // 降序实测(gcc x86-64):
sizeof(struct C) = 24 ; char 后填 7 字节 + 末尾填 7 字节,两个"洞"
sizeof(struct D) = 16 ; double 在前,两个 char 挤在一起,只末尾填 6 字节同样的字段,重排后 24 → 16 字节,省了 1/3 内存。 内存布局:
struct C(24 字节,有洞):
[char][7 padding][double][char][7 padding]
0 1..7 8..15 16 17..23
struct D(16 字节,紧凑):
[double][char][char][6 padding]
0..7 8 9 10..15三、缓存行与结构体数组
结构体的布局直接影响缓存命中——这是从"省内存"到"快访问"的跃迁:
3.1 缓存行(cache line)
CPU 缓存以 64 字节为一个"缓存行"为单位加载。访问一个字段,会把整行 64 字节都拉进缓存:
- 结构体 < 64 字节 → 一个结构体可能和邻居共享一行,遍历数组时缓存友好。
- 结构体 > 64 字节、字段又散乱 → 高频访问的字段可能横跨多个缓存行,命中率下降。
3.2 伪共享(false sharing)—— 多线程的隐形杀手
两个线程各自更新不同的变量,但这两个变量落在同一个缓存行:
struct {
int a; // 线程 1 只改 a
int b; // 线程 2 只改 b
} shared; // a 和 b 在同一 64 字节缓存行里!线程 1 改 a 会让整个缓存行失效,线程 2 改 b 也会让整行失效——两个线程虽然改不同变量,却在不停互踢对方的缓存行,性能暴跌几十倍。这就是伪共享。
对策:让每个线程的变量独占缓存行:
struct alignas(64) {
int a;
char padding[60]; // 填满一个缓存行
} per_thread;详见 伪共享实验。
四、packed 的代价
__attribute__((packed)) 取消 padding:
sizeof(struct packed) = 5 ; char + int,无填充省了 3 字节,但代价:
| 代价 | 说明 |
|---|---|
| 非对齐访问慢 | int 落在非 4 对齐地址,CPU 两次事务拼接 |
| 部分架构崩溃 | 老 ARM 等架构非对齐访问直接触发异常 |
| 编译器可能"保守" | 无法假设对齐,生成更慢的访问序列 |
适用场景:只用于解析外部固定格式(网络包、文件头),不用于热点数据。
五、与本站主线衔接
- L3 缓存:缓存行、局部性、伪共享的完整原理,见 L3 内存子系统。
- TLB:结构体对齐与大页的关系,见 L3 TLB。
- 伪共享实验:可运行的演示,见 demos/false-sharing。
- 结构体用法:字段重排的工程实践,见 结构体内存对齐。
一句话总结
对齐是硬件的访存规则(非对齐要读两次)、padding 是编译器的补丁(字段降序排省内存)、缓存行是性能的分水岭(伪共享让多线程互踢缓存行暴跌);理解结构体布局,就是把"内存怎么排"变成"程序怎么快"。