堆与栈
理解堆(Heap)和栈(Stack)是掌握 Rust 内存管理的关键。
栈(Stack)
栈是一种 LIFO(后进先出)的数据结构,用于存储局部变量和函数调用信息。
栈的特点
- ⚡ 快速分配和释放
- 📏 大小固定,编译时确定
- 🔄 自动管理,函数返回时自动清理
- 📦 连续内存
栈上分配的数据
fn main() {
let x = 5; // 栈上的 i32
let y = true; // 栈上的 bool
let arr = [1, 2, 3]; // 栈上的数组
println!("{}, {}, {:?}", x, y, arr);
} // x, y, arr 离开作用域,自动释放
堆(Heap)
堆是一个大的内存池,用于动态大小的数据。
堆的特点
- 🐌 分配较慢(需要分配器)
- 📐 大小动态,运行时确定
- 🔧 手动管理(Rust 通过所有权自动化)
- 🗂️ 非连续内存
堆上分配的数据
fn main() {
let s = String::from("hello"); // 堆上的 String
let v = vec![1, 2, 3]; // 堆上的 Vec
let b = Box::new(5); // 堆上的 Box
println!("{}, {:?}, {}", s, v, b);
} // s, v, b 离开作用域,堆内存被释放
栈与堆的对比
Loading diagram...
| 特性 | 栈 | 堆 |
|---|---|---|
| 速度 | 快 | 较慢 |
| 大小 | 固定(编译时) | 动态(运行时) |
| 管理 | 自动 | 手动(Rust 自动化) |
| 生命周期 | 作用域 | 显式控制 |
| 典型用途 | 局部变量 | 大对象、动态大小 |
栈帧
每次函数调用都会创建一个栈帧:
fn main() { // main 的栈帧
let x = 5;
let y = foo(x); // foo 的栈帧入栈
println!("{}", y);
} // main 的栈帧出栈
fn foo(n: i32) -> i32 {
let result = n * 2;
result
} // foo 的栈帧出栈
栈帧包含:
- 局部变量
- 参数
- 返回地址
- 保存的寄存器
栈溢出
栈空间有限(通常几 MB),递归过深会导致栈溢出:
fn infinite_recursion() {
infinite_recursion(); // ⚠️ 栈溢出!
}
// 避免:使用迭代或尾递归优化
fn safe_loop() {
let mut i = 0;
loop {
i += 1;
if i > 1000000 {
break;
}
}
}
何时使用堆
使用 Box<T> 将数据放在堆上:
fn main() {
// 递归类型必须使用 Box(大小固定)
enum List {
Cons(i32, Box<List>),
Nil,
}
// 大对象放在堆上
let large_array = Box::new([0; 1000000]);
// 需要长生命周期的数据
let heap_data = Box::new(42);
}
String vs &str
fn main() {
// &str:指向栈或静态内存的字符串切片
let s: &str = "hello"; // 字符串字面量(静态内存)
// String:堆分配的字符串
let mut s = String::from("hello");
s.push_str(", world"); // 可以增长
println!("{}", s);
}
内存布局:
栈:
┌──────────────┐
│ s: &str │ → 静态内存中的 "hello"
├──────────────┤
│ s: String │
│ ├─ ptr ─────┼─→ 堆: "hello, world"
│ ├─ len: 12 │
│ └─ cap: 12 │
└──────────────┘
Vec 的内存布局
fn main() {
let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);
println!("长度: {}, 容量: {}", v.len(), v.capacity());
}
内存布局:
栈:
┌──────────────┐
│ v: Vec<i32> │
│ ├─ ptr ─────┼─→ 堆: [1, 2, 3, _, _, ...]
│ ├─ len: 3 │
│ └─ cap: 8 │
└──────────────┘
Rc 和 Arc
多个所有者共享堆数据:
use std::rc::Rc;
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a); // 引用计数 +1
let c = Rc::clone(&a); // 引用计数 +1
println!("引用计数: {}", Rc::strong_count(&a)); // 3
}
实践建议
✅ 推荐做法
// 1. 优先使用栈(默认)
fn process_number(x: i32) -> i32 {
x * 2
}
// 2. 大对象或动态大小使用堆
fn create_large_buffer() -> Vec<u8> {
vec![0; 1000000]
}
// 3. 需要所有权转移的场景使用 Box
fn create_node() -> Box<Node> {
Box::new(Node { value: 42, next: None })
}
struct Node {
value: i32,
next: Option<Box<Node>>,
}
❌ 避免做法
// ❌ 不必要的 Box
fn bad() -> Box<i32> {
Box::new(42) // i32 很小,没必要放堆上
}
// 更好:直接返回值
fn good() -> i32 {
42
}
// ❌ 栈上分配过大的数组
fn very_bad() {
let huge = [0; 10_000_000]; // 可能栈溢出
}
// 更好:使用 Vec
fn better() {
let huge = vec![0; 10_000_000]; // 堆分配
}
性能考虑
栈分配:
- ⚡ 快速(只需移动栈指针)
- 🔄 缓存友好(局部性好)
堆分配:
- 🐌 较慢(需要分配器查找空闲块)
- 💾 可能缓存未命中
use std::time::Instant;
fn main() {
let start = Instant::now();
// 栈分配(快)
for _ in 0..1_000_000 {
let _x = [0; 100];
}
println!("栈: {:?}", start.elapsed());
let start = Instant::now();
// 堆分配(慢)
for _ in 0..1_000_000 {
let _v = vec![0; 100];
}
println!("堆: {:?}", start.elapsed());
}
小结
- ✅ 栈:快速、固定大小、自动管理
- ✅ 堆:灵活、动态大小、显式管理
- ✅ Rust 通过所有权自动管理堆内存
- ✅ 优先使用栈,必要时使用堆
- ✅
Box,Vec,String等使用堆分配 - ✅ 理解内存布局有助于优化性能