内存布局 (Memory layout)
我们已经从操作层面看过了所有权 (ownership) 和引用 (reference)——能做什么、不能做什么。 现在是时候掀开盖子看看:聊聊内存 (memory) 吧。
栈与堆 (Stack and heap)
谈到内存时,你常常会听人提起栈 (stack) 和堆 (heap)。
它们是程序用来存储数据的两种不同的内存区域。
我们先从栈讲起。
栈 (Stack)
栈 (stack) 是一种 LIFO(Last In, First Out,后进先出)数据结构。
当你调用一个函数时,一个新的栈帧 (stack frame) 会被压到栈顶。这个栈帧用于存储函数的参数、局部变量以及一些"簿记 (bookkeeping)"值。
当函数返回时,对应的栈帧会从栈上弹出1。
+-----------------+
| frame for func1 |
+-----------------+
|
| 调用 func2
v
+-----------------+
| frame for func2 |
+-----------------+
| frame for func1 |
+-----------------+
|
| func2 返回
v
+-----------------+
| frame for func1 |
+-----------------+
从操作层面看,栈上的分配/释放非常快。
我们总是从栈顶压入和弹出数据,因此不需要去搜索可用内存。
我们也不必担心碎片 (fragmentation):栈是一整块连续 (contiguous) 的内存。
Rust
Rust 经常把数据放在栈上。
你的函数有一个 u32 类型的输入参数?那 32 位会放在栈上。
你定义了一个 i64 类型的局部变量?那 64 位也会放在栈上。
这一切运转良好,是因为这些整数的大小在编译期就已知,因此编译后的程序知道为它们在栈上保留多少空间。
std::mem::size_of
你可以使用 std::mem::size_of 函数来查看某个类型在栈上会占用多少空间。
例如对 u8:
// 这个奇怪的语法 (`::<u8>`) 我们之后再讲。
// 现在先忽略它。
assert_eq!(std::mem::size_of::<u8>(), 1);
结果是 1,这很合理,因为 u8 是 8 位长,也就是 1 字节。
1
如果你有嵌套的函数调用,每个函数被调用时都把它的数据压到栈上, 但要等到最内层的函数返回时才会弹出。 如果嵌套调用太深,你可能会用尽栈空间——栈不是无限的! 这就叫 栈溢出 (stack overflow)。
原文链接:英文原文