内存布局

我们从操作的角度探讨了所有权和引用——你能用它们做什么和不能做什么。现在是时候深入了解幕后情况了:让我们谈谈内存

栈与堆

在讨论内存时,你经常会听到人们谈论。这两个不同的内存区域被程序用来存储数据。

让我们从栈开始说起。

是一种后进先出(Last In, First Out, LIFO)的数据结构。当你调用一个函数时,一个新的栈帧会被添加到栈顶。这个栈帧存储了函数的参数、局部变量和一些“簿记”值。当函数返回时,这个栈帧就会从栈中弹出1

                                 +-----------------+
                       func2     | frame for func2 |   func2
+-----------------+  is called   +-----------------+  returns   +-----------------+
| frame for func1 | -----------> | frame for func1 | ---------> | frame for func1 |
+-----------------+              +-----------------+            +-----------------+

从操作角度看,栈的分配/释放非常快。我们总是在栈顶推入和弹出数据,因此不需要搜索空闲内存。我们也不必担心碎片化问题:栈是一块连续的内存。

Rust中的栈

Rust经常在栈上分配数据。你的函数有一个u32类型的输入参数吗?那32位就会在栈上。你定义了一个i64类型的局部变量吗?那64位也会在栈上。这一切运作得很顺利,因为这些整数的大小在编译时就已经知道了,因此编译后的程序知道需要为它们在栈上预留多少空间。

std::mem::size_of

你可以使用std::mem::size_of函数验证类型在栈上会占用多少空间。

比如对于u8

#![allow(unused)]
fn main() {
// 我们稍后会解释这种有趣的语法(`::<String>`)。现在先忽略它。
assert_eq!(std::mem::size_of::<u8>(), 1);
}

1是合理的,因为u8是8位长,或者说1字节。

参考

  • 本节练习位于 exercises/03_ticket_v1/08_stack
1

如果你有嵌套的函数调用,每个函数在被调用时都会将自己的数据推到栈上,但直到最内层的函数返回才将其弹出。 如果嵌套的函数调用太多,可能会耗尽栈空间——栈不是无限大的!这就是所谓的栈溢出