Rust 栈与堆

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

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

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


栈可以看作是一叠书。当我们添加更多书时,我们将它们放在最上面。当我们拿书时,我们从最上面拿。

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

  • 添加数据称为“压栈”。
  • 移除数据称为“出栈”。

这种现象在编程中被称为“后进先出 (LIFO)”。

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

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

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

fn main() {
    let x = 111;
    
    foo();
}

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

main() 执行时,我们将一个 32 位整数(x)分配给栈帧。

地址 名称
0 x 111

在表中,“地址”列指的是 RAM 的内存地址。

它从0开始,直到您的计算机有多少 RAM(字节数)。“名称”列指的是变量,“”列指的是变量的值。

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

地址 名称
2 z 333
1 y 999
0 x 111

数字012不使用计算机实际使用的地址值。实际上,地址会根据值以一定的字节数分隔。

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

地址 名称
0 x 111

最后,main() 完成,所有内容都消失了。

Rust 会自动在栈内和栈外进行内存的分配和释放。


与栈相反,大多数时候我们需要将变量(内存)传递给不同的函数,并让它们比单个函数的执行时间更长地存活。这时我们可以使用堆。

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

fn main() {
    let x = Box::new(100);
    let y = 222;
    
    println!("x = {}, y = {}", x, y);
}

输出

x = 100, y = 222

让我们可视化上面例子中调用 main() 时的情况。

地址 名称
1 y 222
0 x ???

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

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

内存现在看起来像这样:

地址 名称
5678 100
1 y 222
0 x → 5678

在这里,变量 x 持有一个指向地址→ 5678的指针,这是一个用于演示的任意地址。堆可以按任何顺序分配和释放。因此,它可能最终拥有不同的地址,并在地址之间产生空隙。

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

地址 名称
1 y 222
0 x ???

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

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


栈与堆的区别

访问栈中的数据速度更快。 访问堆中的数据速度较慢。
栈上的内存管理是可预测且简单的。 堆的内存管理(任意大小)比较复杂。
Rust 默认在栈上分配。 使用 Box 在堆上分配。
原始类型和函数的局部变量在栈上分配。 大小可变的数据类型,如 StringVectorBox 等,在堆上分配。

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战