内存布局
我们从操作的角度探讨了所有权和引用——你能用它们做什么和不能做什么。现在是时候深入了解幕后情况了:让我们谈谈内存。
栈与堆
在讨论内存时,你经常会听到人们谈论栈和堆。这两个不同的内存区域被程序用来存储数据。
让我们从栈开始说起。
栈
栈是一种后进先出(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
如果你有嵌套的函数调用,每个函数在被调用时都会将自己的数据推到栈上,但直到最内层的函数返回才将其弹出。 如果嵌套的函数调用太多,可能会耗尽栈空间——栈不是无限大的!这就是所谓的栈溢出。