在 Rust 中,内存分配主要通过栈(Stack)和堆(Heap)进行,结合所有权系统实现安全高效的内存管理。
一、Rust 内存管理的核心思想
“编译时检查 + 明确的所有权”
Rust 不需要垃圾回收(GC),也不依赖手动内存管理(如 C/C++)。它通过编译时的规则检查,确保内存安全且高效,最终生成类似手动管理的高效代码。
二、四大核心机制
1. 所有权(Ownership)
- 核心规则:
- 每个值有且只有一个所有者(变量)。
- 当所有者离开作用域(如函数结束),值会被自动释放。
- 赋值或传参时,默认是“移动”(move),原所有者失效。
rust
fn main() {
let s1 = String::from("hello"); // s1 是所有者
let s2 = s1; // s1 的值被移动到 s2,s1 失效!
// println!("{}", s1); // 这里会报错!
}
2. 借用(Borrowing)
- 引用(Reference):允许临时访问值,但不拥有它。
- 规则:
- 同一时间,要么只能有一个可变引用(
&mut
),要么有多个不可变引用(&
)。 - 引用必须始终有效(不能悬空)。
- 同一时间,要么只能有一个可变引用(
rust
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 不可变借用
let r2 = &s; // 另一个不可变借用(允许)
// let r3 = &mut s; // 这里报错!不能同时存在可变和不可变借用
println!("{}, {}", r1, r2);
}
3. 生命周期(Lifetime)
- 作用:确保引用在有效范围内使用,避免悬垂指针。
- 标注:编译器多数情况能自动推断,复杂时需要手动标注。
rust
// 告诉编译器:返回的引用至少和输入的某个引用活的一样长
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
4. 智能指针(Smart Pointers)
- 用途:管理堆内存,扩展所有权机制。
- 常见类型:
Box<T>
:将值分配在堆上,用于明确数据大小或转移所有权。Rc<T>
:引用计数,允许同一数据多个只读所有者(仅单线程)。Arc<T>
:线程安全的引用计数(多线程用)。RefCell<T>
:运行时检查借用规则(允许“内部可变性”)。
rust
use std::rc::Rc;
fn main() {
let data = Rc::new(5); // 引用计数 +1
let data_clone = Rc::clone(&data); // 引用计数 +1(共享)
println!("Count: {}", Rc::strong_count(&data)); // 输出 2
}
三、堆(Heap)与栈(Stack)
- 栈:自动分配/释放,存放固定大小的数据(如整数、结构体)。
- 堆:手动请求内存(通过
Box
、String
等),存放动态大小数据。 - Rust 的策略:
- 默认在栈上分配(高效)。
- 用
Box::new()
或智能指针将数据移到堆上。 - 离开作用域时,堆内存自动释放(通过
Drop
trait)。
栈内存分配::
- 特点:分配速度快、大小固定、自动释放。
- 适用场景:存储基本类型(如 i32、f64)和固定大小的结构体。
堆内存分配:
- 特点:动态分配、生命周期灵活、需要手动管理(通过智能指针)。
- 适用场景:动态大小数据(如字符串、动态数组)或大对象。
四、内存安全保证
Rust 在编译时检查以下问题:
- 空指针:用
Option<T>
替代null
。 - 野指针:通过生命周期和借用规则避免。
- 双重释放:所有权机制确保每个值只释放一次。
- 数据竞争:借用规则防止同时读写。
rust
fn no_dangling() -> String {
let s = String::from("hello");
s // 返回 s,所有权转移出去,不会在函数结束时释放!
}
五、与其他语言对比
特性 | C/C++ | Java/Go | Rust |
---|---|---|---|
内存管理 | 手动 | 垃圾回收(GC) | 编译时所有权 |
性能 | 最高 | 有 GC 开销 | 接近 C/C++ |
安全性 | 易出错 | 较安全 | 编译时保证安全 |
学习曲线 | 中等 | 较低 | 较高 |