内存布局

理解 Rust 如何在内存中组织数据是编写高效代码的关键。

基本类型的大小

use std::mem;

fn main() {
    println!("bool: {} bytes", mem::size_of::<bool>());      // 1
    println!("i32: {} bytes", mem::size_of::<i32>());        // 4
    println!("i64: {} bytes", mem::size_of::<i64>());        // 8
    println!("f64: {} bytes", mem::size_of::<f64>());        // 8
    println!("char: {} bytes", mem::size_of::<char>());      // 4
    println!("&i32: {} bytes", mem::size_of::<&i32>());      // 8 (64位系统)
}

结构体布局

#[repr(C)]  // 使用 C 语言的布局规则
struct Point {
    x: i32,  // 4 bytes
    y: i32,  // 4 bytes
}  // 总共 8 bytes

#[repr(C)]
struct MixedStruct {
    a: u8,   // 1 byte
    // 填充 3 bytes
    b: u32,  // 4 bytes
    c: u16,  // 2 bytes
    // 填充 2 bytes
}  // 总共 12 bytes(有对齐)

fn main() {
    println!("Point: {} bytes", mem::size_of::<Point>());
    println!("MixedStruct: {} bytes", mem::size_of::<MixedStruct>());
}

内存对齐

Rust 会自动添加填充(padding)以满足对齐要求:

struct Aligned {
    a: u8,   // 1 byte
    // 填充 7 bytes
    b: u64,  // 8 bytes
    c: u8,   // 1 byte
    // 填充 7 bytes
}  // 24 bytes(不是 10 bytes!)

// 优化布局:将大字段放在前面
struct Optimized {
    b: u64,  // 8 bytes
    a: u8,   // 1 byte
    c: u8,   // 1 byte
    // 填充 6 bytes
}  // 16 bytes

枚举的布局

enum Message {
    Quit,                       // 0 bytes
    Move { x: i32, y: i32 },    // 8 bytes
    Write(String),              // 24 bytes
}

// 枚举大小 = 判别式(discriminant) + 最大变体的大小
// 约 32 bytes(24 + 8 判别式 + 对齐)

fn main() {
    println!("Message: {} bytes", mem::size_of::<Message>());
}

零大小类型(ZST)

struct Unit;
struct Empty {}

fn main() {
    println!("Unit: {} bytes", mem::size_of::<Unit>());    // 0
    println!("Empty: {} bytes", mem::size_of::<Empty>());  // 0
    println!("(): {} bytes", mem::size_of::<()>());        // 0
}

ZST 的优势:

  • 不占用内存
  • 编译器可以优化掉

3D 内存布局可视化

💡 拖动旋转 | 滚轮缩放 | 点击高亮

胖指针

某些类型的引用包含额外元数据:

fn main() {
    // 普通引用:1 个指针
    let x: i32 = 42;
    let r: &i32 = &x;
    println!("&i32: {} bytes", mem::size_of_val(&r));  // 8

    // 切片引用:指针 + 长度
    let arr = [1, 2, 3, 4, 5];
    let slice: &[i32] = &arr;
    println!("&[i32]: {} bytes", mem::size_of_val(&slice));  // 16

    // trait 对象:指针 + vtable 指针
    let b: &dyn std::fmt::Debug = &42;
    println!("&dyn Debug: {} bytes", mem::size_of_val(&b));  // 16
}

表示优化

Option<&T> 优化

fn main() {
    println!("Option<&i32>: {} bytes",
        mem::size_of::<Option<&i32>>());  // 8(不是 16!)

    println!("&i32: {} bytes",
        mem::size_of::<&i32>());  // 8
}

Rust 利用引用不能为 NULL 的特性,用 NULL 表示 None

非零类型

use std::num::NonZeroU32;

fn main() {
    println!("Option<u32>: {} bytes",
        mem::size_of::<Option<u32>>());  // 8

    println!("Option<NonZeroU32>: {} bytes",
        mem::size_of::<Option<NonZeroU32>>());  // 4
}

内存布局属性

// 使用 C 布局
#[repr(C)]
struct CLayout {
    a: u8,
    b: u32,
}

// 紧凑布局(移除填充)
#[repr(packed)]
struct Packed {
    a: u8,
    b: u32,
}  // 5 bytes(危险:未对齐访问)

// 透明布局(单字段)
#[repr(transparent)]
struct Wrapper(u32);

实践建议

✅ 推荐做法

// 1. 将大字段放在前面以减少填充
#[repr(C)]
struct Optimized {
    large: u64,   // 8 bytes
    medium: u32,  // 4 bytes
    small: u8,    // 1 byte
    // 只需填充 3 bytes
}

// 2. 使用 #[repr(C)] 与 C 互操作
#[repr(C)]
struct FFIStruct {
    x: i32,
    y: i32,
}

// 3. 检查大小和对齐
const _: () = assert!(mem::size_of::<MyStruct>() == 16);

❌ 避免做法

// ❌ 不必要的 #[repr(packed)]
#[repr(packed)]
struct Bad {
    a: u32,  // 未对齐访问可能很慢
}

// ❌ 忽略内存布局
// 在性能关键的代码中应该考虑布局
struct Unoptimized {
    a: u8,   // 1 byte
    b: u64,  // 填充 7 bytes
    c: u8,   // 填充 7 bytes
}  // 浪费 14 bytes

小结

  • ✅ 不同类型有不同的内存大小
  • ✅ 结构体会有对齐填充
  • ✅ 优化字段顺序可以减少内存使用
  • ✅ ZST 不占用内存
  • ✅ Rust 对某些类型做了表示优化
  • ✅ 使用 mem::size_of 检查大小