跳到主要内容

内存布局

问题

Rust 中数据的内存布局是怎样的?结构体对齐和枚举优化是如何工作的?

答案

栈与堆

特性
分配速度极快(移动指针)较慢(分配器查找)
释放自动(作用域结束)手动/RAII
大小编译时确定运行时确定
访问速度更快(缓存友好)较慢(指针追踪)
默认大小~8MB(线程栈)受系统内存限制
let x: i32 = 42;              // 栈:4 字节
let s: String = "hello".into(); // 栈:24 字节(ptr+len+cap),堆:5 字节数据
let b: Box<[u8; 1000]> = Box::new([0; 1000]); // 栈:8 字节指针,堆:1000 字节

基本类型大小

use std::mem::size_of;

size_of::<bool>(); // 1
size_of::<i8>(); // 1
size_of::<i32>(); // 4
size_of::<i64>(); // 8
size_of::<f64>(); // 8
size_of::<char>(); // 4(Unicode 码点)
size_of::<&str>(); // 16(ptr + len)
size_of::<String>(); // 24(ptr + len + cap)
size_of::<Vec<u8>>(); // 24
size_of::<Option<&u8>>(); // 8(空指针优化!)
size_of::<Box<i32>>(); // 8(指针)

结构体对齐

// 编译器可能重排字段以减少填充
struct A {
a: u8, // 1 字节
b: u64, // 8 字节
c: u16, // 2 字节
}
// 默认布局可能重排为 b, c, a → 大小 16 而非 24

#[repr(C)] // 不重排,保持声明顺序
struct B {
a: u8, // 1 + 7 字节填充
b: u64, // 8
c: u16, // 2 + 6 字节填充
}
// repr(C) 大小 = 24

枚举布局与 Niche 优化

// 普通枚举
enum Color { Red, Green, Blue }
// 大小: 1 字节(判别值 0, 1, 2)

// 携带数据的枚举
enum Shape {
Circle(f64), // 8 字节
Rect(f64, f64), // 16 字节
}
// 大小 = 判别值(8) + 最大变体(16) = 24

// Niche 优化:Option<&T> 与 &T 大小相同!
size_of::<Option<&u8>>(); // 8(None 用空指针表示)
size_of::<Option<Box<u8>>>(); // 8
size_of::<Option<NonZeroU32>>(); // 4

repr 属性

属性效果
#[repr(Rust)]默认,编译器自由优化布局
#[repr(C)]兼容 C ABI,不重排字段
#[repr(transparent)]与内部类型完全相同的布局
#[repr(packed)]移除所有填充(小心对齐问题)
#[repr(align(N))]指定最小对齐

常见面试问题

Q1: 为什么 Option<&T> 是零开销的?

答案

编译器知道引用永远不为 null,所以用空指针(0x0)表示 None。这叫 Niche 优化——利用类型中"不可能的值"来编码判别信息。同样适用于 BoxNonZero* 类型。

Q2: String&str 的内存布局有什么区别?

答案

String(24 字节,栈上):
┌─────────┬─────┬──────────┐
│ ptr (8B) │len │ capacity │
└────┬─────┴──┬──┴──────────┘
│ │
▼ │
堆上数据 "hello"

&str(16 字节,栈上):
┌─────────┬─────┐
│ ptr (8B) │len │
└────┬─────┴─────┘


任意位置的 UTF-8 数据

String 多一个 capacity 字段,并拥有堆上数据的所有权。

Q3: 如何查看类型的内存布局?

答案

use std::mem::{size_of, align_of, size_of_val};

println!("大小: {}", size_of::<MyType>());
println!("对齐: {}", align_of::<MyType>());
println!("值大小: {}", size_of_val(&my_value));

更详细的分析可以使用 -Z print-type-sizes(nightly)或第三方工具如 memoffset

相关链接