内存布局
问题
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 优化——利用类型中"不可能的值"来编码判别信息。同样适用于 Box、NonZero* 类型。
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。