bigywater.tech
Rust · 112

#[repr(C)] 与内存布局

为什么同样的字段,repr(C) 和默认布局大小不同?填充在哪里?怎么用声明顺序省空间?packed 为什么不能取引用?这篇用 offset_of 与字节转储直接把布局看出来。

8 对 12 字节
同样 (u8, u32, u16) 的 repr(Rust) 与 repr(C) 大小
24 对 16 字节
repr(C) 里字段声明顺序不同造成的大小差
E0793
引用 packed 结构体字段的错误码

repr(Rust) 与 repr(C)

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    repr(Rust): size 8 align 4  偏移 a=6 b=0 c=4
    repr(C):    size 12 align 4  偏移 a=0 b=4 c=8
    字段本身合计 7 字节;repr(C) 多出的 5 字节是填充(padding)

对齐与填充的更多细节见Struct 对齐与 Padding。repr(Rust) 的布局没有稳定保证,这里观察到的是 rustc 1.98.1 在 aarch64 上的结果。

声明顺序与填充

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    repr(C) 声明顺序 u8,u64,u8,u32: 24 字节
    repr(C) 声明顺序 u64,u32,u8,u8: 16 字节(按大小降序,填充最少)
    repr(Rust) 同样四个字段(任意声明顺序): 16 字节(编译器自己重排)

packed 与 align

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    packed: size 7 align 1(没有填充,各字段紧挨着)
    align(16) 的 struct,只含一个 u32: size 16 align 16(大小被补到对齐的倍数)
    按值读 packed 字段是安全的: b = 2
  源码:
      1 | #[repr(C, packed)] struct P { a: u8, b: u32 }
      2 | fn main() { let p = P { a: 1, b: 2 }; let r = &p.b; println!("{}", r); }
  编译:失败
    error[E0793]: reference to field of packed struct is unaligned
     --> <实验目录>/src/u112d.rs:2:47
      |
    2 | fn main() { let p = P { a: 1, b: 2 }; let r = &p.b; println!("{}", r); }
      |                                               ^^^^
      |
      = note: this struct is 1-byte aligned, but the type of this field may require higher alignment
      = note: creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
      = help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0793`.

枚举的 repr

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    默认 enum E0 {A,B,C}: 1  repr(u8): 1  repr(C): 4  repr(u32): 4
    带数据 enum {A(u8), B(u32), C}: 默认 8  repr(C): 8
    E1::C as u8 = 200(repr(u8) 的判别值可以读出来)

枚举布局见Enum 的内存布局。

字节转储

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    repr(C) 结构体的 12 个字节(十六进制): aa 00 00 00 44 33 22 11 ef be 00 00
    tag 在字节 0,len 从字节 4 开始(小端:44 33 22 11),flags 从字节 8 开始(小端:ef be);其余是填充(内容未指定)

分步演示

repr(C)
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。repr(Rust) 的字段重排是编译器当前的实现细节,不是语言保证
    • 没有验证:与 C 编译器实际生成布局的一致性(没有链接 C 代码)、repr(transparent)、big-endian 目标上的字节序、union 的布局、位域
    京ICP备2026067502号-1京公网安备11010502063487号