bigywater.tech
Rust · 114

Enum 的内存布局

Option> 为什么和 Box 一样大?枚举的标签放在哪里?什么类型有 niche?这篇用编译器自己的布局输出和 transmute 读内存,把枚举布局与空指针优化量出来。

4 字节
Option 的大小(Option 是 8 字节)
1 字节
Option>> 的大小
24 字节
Msg 枚举的大小(与 String 相同,没有单独的标签)

Option 与 niche

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      u32                     4 字节   Option<u32>                 8 字节   多出 tag
      u8                      1 字节   Option<u8>                  2 字节   多出 tag
      NonZeroU32              4 字节   Option<NonZeroU32>          4 字节   Option 不加大小(niche)
      NonZeroU8               1 字节   Option<NonZeroU8>           1 字节   Option 不加大小(niche)
      bool                    1 字节   Option<bool>                1 字节   Option 不加大小(niche)
      char                    4 字节   Option<char>                4 字节   Option 不加大小(niche)
      Dir                     1 字节   Option<Dir>                 1 字节   Option 不加大小(niche)
      &u64                    8 字节   Option<&u64>                8 字节   Option 不加大小(niche)
      Box<u64>                8 字节   Option<Box<u64>>            8 字节   Option 不加大小(niche)
      Rc<u64>                 8 字节   Option<Rc<u64>>             8 字节   Option 不加大小(niche)
      Vec<u8>                24 字节   Option<Vec<u8>>            24 字节   Option 不加大小(niche)
      String                 24 字节   Option<String>             24 字节   Option 不加大小(niche)
      f64                     8 字节   Option<f64>                16 字节   多出 tag
      Option<Option<bool>> 1 字节  Option<Option<Option<bool>>> 1 字节(bool 只用了 0、1 两个值,其余位模式都可以拿来当标签)

Option<&T> 的空指针优化见Option<&T> 的空指针优化。

带数据的枚举

实测 · 本机执行输出
  type: `Shape`: 28 bytes, alignment: 4 bytes
      discriminant: 4 bytes
      variant `Poly`: 24 bytes
          field `.0`: 24 bytes
      variant `Rect`: 8 bytes
          field `.0`: 4 bytes
          field `.1`: 4 bytes
      variant `Circle`: 4 bytes
          field `.0`: 4 bytes
      variant `Dot`: 0 bytes
  type: `Msg`: 24 bytes, alignment: 8 bytes
      variant `Write`: 24 bytes
          field `.0`: 24 bytes
      variant `Move`: 16 bytes
          padding: 8 bytes
          field `.x`: 4 bytes, alignment: 4 bytes
          field `.y`: 4 bytes
      variant `Color`: 11 bytes
          padding: 8 bytes
          field `.0`: 1 bytes, alignment: 1 bytes
          field `.1`: 1 bytes
          field `.2`: 1 bytes
      variant `Quit`: 0 bytes

布局输出用 RUSTC_BOOTSTRAP=1 -Z print-type-sizes,枚举布局不是稳定保证。repr(C) 与 repr(u8) 的区别见#[repr(C)] 与内存布局。

判别值

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    Op::Add as i32 = 10  Op::Sub = 20  Op::Mul(接着前一个 +1) = 21
    discriminant(&Data::A(1)) == discriminant(&Data::A(99)): true
    discriminant(&Data::A(1)) == discriminant(&Data::B(String::new())): false
    数字转回枚举没有内置的安全方法:要手写 match 或用 TryFrom(下面是编译器的报错)
  源码:
      1 | #[derive(Debug)] enum Op { Add = 10, Sub = 20 }
      2 | fn main() { let o: Op = 10 as Op; println!("{:?}", o); }
  编译:失败
    error[E0605]: non-primitive cast: `i32` as `Op`
     --> <实验目录>/src/u114d.rs:2:25
      |
    2 | fn main() { let o: Op = 10 as Op; println!("{:?}", o); }
      |                         ^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0605`.

niche 的真实字节

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    Option<&u64>:None = 0x0(空指针);Some 的值 = 指针本身(非 0): true
    Option<bool>:None = 2,Some(true) = 1,Some(false) = 0(用 bool 不会出现的 2 作为 None)
    Option<u32>(8 字节):None 的低 4 字节 tag = 0,Some(7) 的 tag = 1

分步演示

枚举布局
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。布局是 rustc 1.98.1 在 aarch64 上的当前结果
    • 没有验证:多变体枚举 niche 选择的具体规则(为什么 Msg 的标签选了 String 的容量字段,只观察到结果)、其它平台、repr(C) 枚举带数据的联合布局、枚举字段重排与大小的关系
    京ICP备2026067502号-1京公网安备11010502063487号