内存对齐:让数据“站队”提速关键
先说一句话内存对齐就是让数据“站在整齐的位置上”方便 CPU 一次就拿到它。一、先看 CPU 是怎么读内存的内存可以想象成一排很长的格子每个格子放 1 个字节并且有编号地址地址 0 1 2 3 4 5 6 7 8 9 10 11 …… [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]但 CPU不是一格一格地读而是一整块一整块地搬比如一次搬 4 个或 8 个字节而且通常从“块的边界”开始搬┌── 第1块 ──┐┌── 第2块 ──┐┌── 第3块 ──┐ 地址 0 1 2 3 4 5 6 7 8 9 10 11用快递打个比方仓库里的货都装在固定的箱子里每箱 4 格 快递员每次只能整箱搬不能从箱子中间开始拿二、对齐和不对齐的区别假设要读一个int4 个字节。✅ 对齐放在地址 4┌── 第1块 ──┐┌── 第2块 ──┐ 地址 0 1 2 3 4 5 6 7 [ int 在这 ]搬 1 次正好整块拿到。❌ 不对齐放在地址 3┌── 第1块 ──┐┌── 第2块 ──┐ 地址 0 1 2 3 4 5 6 7 [ int 在这 ] ↑ 横跨了两块CPU 要1. 搬第 1 块取出最后 1 个字节 2. 搬第 2 块取出前 3 个字节 3. 把它们拼起来搬 2 次还要拼接慢了。对齐的规则一般规则很简单一个数据的地址应该是它自身大小的整数倍。类型大小应该放在char1 字节任意地址short2 字节0、2、4、6 ……int / float4 字节0、4、8、12 ……double / 指针64位8 字节0、8、16、24 ……三、最常见的体现结构体变大了看这个结构体structA{chara;// 1 字节intb;// 4 字节charc;// 1 字节};直觉上1 4 1 6 字节。但实际sizeof(struct A)通常是12 字节。为什么地址 0 1 2 3 4 5 6 7 8 9 10 11 [a] [空][空][空][ b b b b ] [c] [空][空][空] └─ 填充 ─┘ └─ 填充 ─┘a放在 0b是 int必须放在 4 的倍数 → 跳过 1、2、3放在 4c放在 8末尾再补 3 个字节让整个结构体是 4 的倍数这些空出来的字节叫填充padding是编译器自动加的。 为什么末尾也要补因为如果是数组struct A arr[2]第二个元素紧挨着第一个不补的话第二个元素里的b就不对齐了。四、内存对齐的好处1. 读写更快数据不跨块CPU 一次就能拿到不用搬两次再拼接。2. 有些 CPU 必须对齐否则直接出错x86大部分电脑不对齐也能读只是可能慢一点 部分 ARM、MIPS 等芯片不对齐的访问可能直接报错、程序崩溃所以对齐能让程序在各种平台上都正常运行。3. 保证读写是“一次完成”的原子性多线程程序里一个线程在写另一个线程在读对齐一次写完别人要么看到旧值要么看到新值 不对齐分两次写别人可能读到“一半旧、一半新”的错误值4. 配合缓存和 SIMD 指令CPU 缓存按“缓存行”通常 64 字节读取数据跨两个缓存行会更慢游戏、图像处理常用的 SIMD 加速指令一次处理多个数很多要求数据按 16 或 32 字节对齐五、内存对齐带来的问题问题 1浪费内存上面的例子6 字节的数据占了 12 字节一半是空的。如果有一百万个这样的结构体就多浪费了好几 MB。问题 2结构体大小和你想的不一样// 以为是 6 字节按 6 字节去读写文件 → 数据全错位fwrite(a,6,1,file);// ❌fwrite(a,sizeof(a),1,file);// ✅ 永远用 sizeof问题 3跨平台、网络传输时容易出错不同编译器、不同 CPU 的对齐规则可能不同电脑 A 把结构体直接发出去含填充12 字节 设备 B 的规则不一样按另一种布局去解析 → 读出来的数据对不上所以写文件格式、网络协议时一般一个字段一个字段地读写而不是直接把整个结构体丢过去。问题 4强行取消对齐可能出事可以用#pragma pack(1)让编译器不加填充#pragmapack(1)structA{chara;intb;charc;};// 正好 6 字节#pragmapack()省了空间但b就不对齐了访问变慢在某些芯片上甚至会崩溃。只在确实需要紧凑布局时用比如解析固定格式的协议数据。六、怎么既对齐又不浪费——调整成员顺序把大的成员放前面小的放后面structB{intb;// 4 字节chara;// 1 字节charc;// 1 字节};地址 0 1 2 3 4 5 6 7 [ b b b b ] [a] [c] [空][空]同样的三个数据从 12 字节变成 8 字节而且依然是对齐的。如果需要更严格的对齐比如 SIMDC/C 可以用alignas(16)floatdata[4];// 让数组按 16 字节对齐一句话总结CPU 是按“整块”读内存的内存对齐就是让数据不跨块这样读写更快、更安全有些芯片还必须这样代价是编译器会塞进一些空字节浪费空间、让结构体大小和预想的不同。把成员按从大到小排列可以减少浪费。