跳到主要内容

Rust栈和堆

提示
  1. 栈的概念与操作:栈是后进先出(LIFO)的内存区域,用于存储编译时已知大小的数据。数据按顺序插入并按相反顺序移除,称为推入栈和从栈弹出。
  2. 堆的使用与特点:堆用于存储动态大小或生命周期不确定的数据。使用Box<T>在堆上分配内存,与栈不同,堆分配的内存可以在不同函数间传递和保持活跃。
  3. 栈和堆的区别:栈访问更快、内存管理简单,主要用于小型固定大小数据。堆访问较慢、内存管理更复杂,用于动态或大型数据,如StringVector等。

栈和堆是我们的 Rust 代码在运行时可用的内存部分。

Rust 是一种内存安全的编程语言。为了确保 Rust 的内存安全,它引入了所有权、引用和借用等概念。

要理解这些概念,我们首先必须了解如何在栈和堆中分配和释放内存。

栈(Stack)

栈可以被想象成一堆书。当我们添加更多书籍时,我们把它们放在堆顶。当我们需要一本书时,我们从顶部取一本。

栈按顺序插入值。它按相反的顺序获取并移除值。

  • 添加数据被称为推入栈(pushing onto the stack)
  • 移除数据被称为从栈弹出(popping off the stack)

这种现象在编程中被称为后进先出(Last In, First Out,LIFO)

存储在栈上的数据必须在编译时具有固定大小。Rust 默认为基本类型在栈上分配内存。

让我们通过一个例子来可视化栈上的内存分配和释放。

fn foo() {
let y = 999;
let z = 333;
}

fn main() {
let x = 111;

foo();
}

在上面的例子中,我们首先调用函数 main()main() 函数有一个变量绑定 x

main() 执行时,我们为栈帧分配一个单独的 32 位整数(x)。

地址名称
0x111

在表格中,"地址" 列指的是 RAM 的内存地址。

它从 0 开始,一直到你的电脑有多少 RAM(字节数)。"名称" 列指的是变量,"值" 列指的是变量的值。

当调用 foo() 时,会分配一个新的栈帧。foo() 函数有两个变量绑定,yz

地址名称
2z333
1y999
0x111

数字 012 不使用计算机实际会用到的地址值。实际上,地址是根据值的大小以某些字节数间隔分开的。

foo() 完成后,其栈帧被释放。

地址名称
0x111

最后,main() 完成,所有内容都消失了。 Rust 自动在栈内外进行内存的分配和释放。

堆(Heap)

与栈不同,我们大多数时候需要将变量(内存)传递给不同的函数,并让它们在单个函数执行的时间之外保持活跃。这时我们可以使用堆。

我们可以使用 Box<T> 类型在堆上分配内存。例如,

fn main() {
let x = Box::new(100);
let y = 222;

println!("x = {}, y = {}", x, y);
}

输出

x = 100, y = 222

让我们可视化上述示例中调用 main() 时的内存情况。

地址名称
1y222
0x???

像之前一样,我们在栈上分配了两个变量,xy

然而,当调用 Box::new() 时,x 的值在堆上分配。因此,x 的实际值是一个指向堆的指针。

现在的内存看起来是这样的:

地址名称
5678100
1y222
0x→ 5678

这里,变量 x 保存了一个指向地址 → 5678 的指针,这个地址是为了演示而随意使用的。堆可以按任何顺序分配和释放。因此,它可以以不同的地址结束,并在地址之间创建空隙。

所以当 x 消失时,它首先释放在堆上分配的内存。

地址名称
1y222
0x???

一旦 main() 完成,我们释放栈帧,所有内容都消失了,释放了所有内存。

我们可以通过转移所有权来使内存活得更久,这样分配 Box 的函数的堆可以比该函数的生命周期更长。要了解更多关于所有权的信息,请访问 Rust 所有权

栈和堆之间的区别

栈(Stack)堆(Heap)
在栈中访问数据更快。在堆中访问数据更慢。
在栈中管理内存是可预测的且简单的。在堆中管理内存(任意大小)是非平凡的。
Rust 默认在栈上分配内存。使用 Box 在堆上分配内存。
基本类型和函数的局部变量在栈上分配。动态大小的数据类型,如 StringVectorBox 等,在堆上分配。