rust 内存对齐概念

rust 内存对齐(C也差不多)

核心就一句话
CPU 读 8 字节的数据,只能从 8 的倍数地址开始读;读 4 字节,只能从 4 的倍数地址开始读。
这就好比:

  • 8 字节的数据必须放在 8 楼、16 楼、24 楼...(地址是 8 的倍数)
  • 4 字节的数据必须放在 4 楼、8 楼、12 楼...(地址是 4 的倍数)
  • 1 字节的数据随便放
    看你的 repr(C) 例子
    struct ReprC { // 按声明顺序
    a: u8, // 大小1,对齐1 → 放地址0 ✓
    b: u64, // 大小8,对齐8 → 必须放8的倍数地址
    c: u16, // 大小2,对齐2 → 必须放2的倍数地址
    }
    放东西的过程(想象你在放箱子):
    地址: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
  • 放 a:u8,1 字节,随便放 → 放地址 0 ✅
    [a]
    0
  • 放 b:u64,8 字节,必须放 8 的倍数地址。但是下一个空位是地址 1,1 不是 8 的倍数,不能放!
  • 地址 1~7 全部浪费掉(这就是 padding)
  • 到地址 8,是 8 的倍数 → 放 b ✅,占 8~15
    [a][ padding ][ b ]
    0 1 2 3 4 5 6 7 8...15
  • 放 c:u16,2 字节,必须放 2 的倍数地址。下一个空位是 16,16 是 2 的倍数 → 放 c ✅,占 16~17
    [a][ padding ][ b ][c]
    0 1...7 8...15 16 17
  • 最后整体也要对齐:结构体本身的对齐是所有成员里最大的(这里是 8),所以大小必须是 8 的倍数。现在是 1+7+8+2=18,但 18 不是 8 的倍数 → 凑到 24(末尾再垫 6 字节 padding)
    [a][ padding ][ b ][c][ padding ]
    0 1...7 8...15 16 17 18...23
    所以 repr(C) 总共 24 字节,里面只有 11 字节是真正的数据,13 字节都是 padding!
    为什么 Rust 默认的更省?
    Rust 默认会帮你重排字段顺序,相当于自动把最大的放前面:
    struct DefaultA { // Rust 编译时悄悄改成下面这样
    b: u64, // 8 → 放地址 0~7
    c: u16, // 2 → 放地址 8~9
    a: u8, // 1 → 放地址 10
    // padding 5 字节 → 凑到 16(8 的倍数)
    }
    // 总共 16 字节,padding 只有 5 字节
    为什么 packed 只有 11 字节?
    packed 的意思是:别管对齐规则了,全部紧挨着放。
  • a 放 0,b 紧挨着放 18,c 紧挨着放 910
  • 总共 11 字节,没有 padding
    但是 CPU 读 b(地址 1)时,地址 1 不是 8 的倍数,CPU 要读两次再拼起来,速度变慢,某些架构上甚至直接崩溃。
    一句话总结:对齐就是 CPU 挑地址"挑食",只吃特定倍数的地址。编译器只好塞 padding 来"喂"它。

Q:然后为什么有这样对齐讲究?
A:CPU 读 8 字节的数据,只能从 8 的倍数地址开始读;读 4 字节,只能从 4 的倍数地址开始读。 这是什么原理?

  • Thought: 2.8s
    这完全是内存总线的硬件设计决定的。
    用货车和仓库来理解
    想象 RAM 是一个仓库,里面是一排排货架,每个货架宽 8 个格子:
    货架 0: [ 0][ 1][ 2][ 3][ 4][ 5][ 6][ 7]
    货架 1: [ 8][ 9][10][11][12][13][14][15]
    货架 2: [16][17][18][19][20][21][22][23]
    CPU 和内存之间的数据总线只有 8 字节宽——相当于一辆货车一次只能运一整排(8 个格子)。
    对齐读取(一次搞定)
    读地址 8 的 u64(8 字节):
    货车去货架 1 → [8][9][10][11][12][13][14][15] → 一次运回 ✅
    未对齐读取(两次搬运)
    读地址 1 的 u64(8 字节):
    货车先去货架 0 → 取 [1][2][3][4][5][6][7](7 个有用,但不够)
    再去货架 1 → 取 [8][9][10][11][12][13][14][15](1 个有用)
    CPU 拆开包装,把两趟的零件拼成一个 u64
    要多跑一趟,还要额外运算去拼接。
    硬件层面更简单
    内存地址的低 3 位(addr & 0b111)就是用来定位在货架中的位置的。当 CPU 发出读地址指令时:
  • 读地址 8 → 0b00_1000 → 低 3 位是 000 → 正好是货架起始 → 一趟拉 8 字节
  • 读地址 1 → 0b00_0001 → 低 3 位是 001 → 不在货架开头 → 硬件得先读货架 0,再读货架 1
    大多数 RISC CPU(ARM、RISC-V)直接不支持未对齐访问,碰到就崩溃。x86 比较"能干"但背后还是要多搬几趟,慢。所以编译器主动塞 padding,就是为了保证每次都是一趟拉完。
posted @ 2026-07-08 15:12  lostkk  阅读(3)  评论(0)    收藏  举报