堆与栈

理解堆(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 等使用堆分配
  • ✅ 理解内存布局有助于优化性能