编程导航Rust话题讨论

Rust

20 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

用餐厅和盘子理解栈和堆

最近在学 Rust。其实之前就学过一段时间,但学到所有权这里就卡住了,概念一多加上当时在专升本,后面就放弃了🥲。趁着国庆假期有整块时间,决定重新捡起来,这次争取把它啃下来。 本文的内容来源于 Rust 官方教程 [What Is Ownership?](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html),在学习所有权(Rust 独有的机制)之前,官方先花了一段篇幅介绍**栈(Stack)和堆(Heap)**。很多从 Java、Python、Go 这类有 GC 的语言转过来的同学(包括我自己)平时很少关心数据到底放在哪里,所以这一段读起来有点抽象,我让 AI 翻译了一下并且加上了一些内容,方便我理解,我感觉写的挺好的,就分享一下。 ## 栈 Stack ![](https://pic.code-nav.cn/post_picture/1608460212774109186/roSafpBfWZvUDNwe.webp) 栈的规则只有一条,叫**后进先出(LIFO)**:最后放上去的盘子,最先被拿走。 ``` 放入(push) 取出(pop) ↓ ↑ ┌───────┐ │ 盘子 3 │ ← 栈顶:只能在这里操作 ├───────┤ │ 盘子 2 │ ├───────┤ │ 盘子 1 │ ← 最先放的,最后才拿走 └───────┘ ``` 栈有一个硬性要求:**每个数据的大小必须在编译时就确定,而且不能变。** 就像每个盘子尺寸都一样,才能整整齐齐摞起来。`i32`(4 字节)、`bool`(1 字节)、`[u8; 10]`(10 字节)这类类型都可以放在栈上。 **栈为什么快?** 因为根本不用找位置,新数据永远放在栈顶。分配内存只是把栈顶指针挪一下,几乎不花时间。 ## 堆 Heap ![堆:餐厅找座位](https://pic.code-nav.cn/post_picture/1608460212774109186/G6AxOO453haHGJUG.webp) 如果数据大小在编译时不知道,或者运行中会变化(比如用户输入的字符串、一个会不断 `push` 的 `Vec`),就没法放在栈上,只能放到**堆**里。 堆就像一家餐厅: 1. 你进门告诉服务员:"我们 4 个人。"(申请 4 个单位的空间) 2. 服务员在店里找一张够大的空桌。(**分配器**搜索空闲内存) 3. 把桌子标记为"已占用",并告诉你:"你们坐 7 号桌。"(返回一个**指针**,也就是内存地址) 4. 朋友晚到了,只要问"7 号桌在哪"就能找到你们。(通过指针访问数据) ![栈和堆的内存结构](https://pic.code-nav.cn/post_picture/1608460212774109186/Q8gfHJhzoFw1Vd0C.webp) ``` 栈(Stack) 堆(Heap) ┌──────────────────┐ ┌─────────────────────────┐ │ x: i32 = 5 │ │ ░░░░ [已占用] ░░░░░░░ │ ├──────────────────┤ │ │ │ s: String │ │ ┌───┬───┬───┬───┬───┐ │ │ ├ ptr ──────────┼───────────►│ │ h │ e │ l │ l │ o │ │ │ ├ len = 5 │ │ └───┴───┴───┴───┴───┘ │ │ └ cap = 5 │ │ ░░░ [空闲] ░░░░░░░░░░ │ └──────────────────┘ └─────────────────────────┘ 大小固定,排列整齐 大小可变,位置随意 ``` 关键点:**指针本身大小是固定的**(64 位系统上是 8 字节),所以指针可以放在栈上,它指向的真实数据放在堆上。`String` 就是这样:栈上存"指针 + 长度 + 容量"三个字段,字符内容存在堆上。 ## 栈和堆的区别 先用一张表把两者放在一起对比: | | 栈 | 堆 | | -------- | -------------------- | ---------------------------- | | 数据大小 | 编译时确定,固定不变 | 编译时未知,运行时可变 | | 分配方式 | 直接放在栈顶 | 先找一块够大的空位,再做记录 | | 访问方式 | 直接读取 | 先读指针,再跳到对应地址 | | 内存布局 | 连续、紧凑 | 分散在内存各处 | | 释放时机 | 函数返回时自动弹出 | 需要有人负责回收 | | 速度 | 快 | 相对慢 | | 类比 | 一摞盘子 | 餐厅找座位 | 其中最直观的差别是速度,堆比栈慢主要体现在两个环节。 **1. 分配:栈不用找位置,堆要找** 往栈上放数据,就像往盘子堆上再放一个,位置永远是栈顶,挪一下栈顶指针就完事了。 往堆上放数据,就像服务员要满店找一张够大的空桌,找到后还要登记"这张桌有人了",步骤多得多。 **2. 访问:栈直接拿,堆要跳一下** 访问栈上的数据可以直接读;访问堆上的数据要先读指针,再跳到指针指向的地址,多了一步。更重要的是,堆上的数据可能散落在内存各处。 还是用餐厅打比方,一个服务员负责点单: ``` 高效(类似栈):A桌 → A桌 → A桌 → B桌 → B桌 → B桌 低效(类似堆):A桌 → B桌 → A桌 → B桌 → A桌 → B桌 ↑ 来回跑,大部分时间花在路上 ``` CPU 也一样:数据在内存里挨得越近,CPU 缓存命中率越高,处理就越快;在内存里跳来跳去就会变慢。 至于"释放时机"这一行的差别,正是后面要讲的重点:栈上的数据随函数调用自动清理,而堆上的数据需要有人管,这也是所有权要解决的问题。 ## 函数调用的实现 调用函数时,参数和局部变量会被压入栈;函数返回时,这些数据被弹出,自动清理,不用操心。 ```rust fn main() { let a = 1; foo(a); } fn foo(x: i32) { let y = 2; } ``` ``` main开始 调用foo foo结束 main结束 ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ y = 2 │ │ │ │ │ │ x = 1 │ foo │ │ (空) │ a = 1 │ │ a = 1 │ main │ a = 1 │ └────────┘ └────────┘ └────────┘ ``` **栈上的数据根本不需要人管理**,因为它的生命周期和函数调用严格绑定。 ## 麻烦的是堆的分配 堆上的数据不会随函数结束自动消失。这就带来了几个问题,继续用餐厅类比: | 问题 | 餐厅类比 | 后果 | | --------------------- | ------------------------ | -------------------------------------------------- | | 谁在用这块数据? | 7 号桌现在到底是谁的? | 搞不清就容易出错 | | 重复数据 | 同一拨人被安排了三张桌子 | 浪费内存 | | 不清理 | 客人走了桌子一直没收 | **内存泄漏**,最后没座位可用 | | 清理两次 / 清理后还用 | 桌子收了,又有人回来坐 | **double free / 悬垂指针**,程序崩溃或出现安全漏洞 | 各语言处理这些问题的方式不同: - **C/C++**:靠程序员手动 `malloc`/`free`,容易出错。 - **Java/Go/Python**:用垃圾回收器(GC)定期巡视、回收,省心但有运行时开销,还有需要单独学习垃圾回收机制😭。 - **Rust**:用**所有权**规则在**编译期**就保证安全。每块堆内存都有唯一的"主人"(owner),主人离开作用域时内存自动释放,没有 GC 开销,也不会忘记释放。 ```rust { let s = String::from("hello"); // 在堆上分配,s 是主人 // 使用 s ... } // s 离开作用域 → Rust 自动调用 drop → 堆内存被释放 ```

给兄弟们整个活... 看到了知乎有个提问“Rust的unsafe到底哪里unsafe了?” 然后...整了个库,用`trust_me`来替代`unsafe`关键字[得意] 让编译器闭嘴,让看代码的人也闭嘴[愉快] #Rust# 项目 Github: https://github.com/egmsia01/shut_up

dust

地址:https://github.com/bootandy/dust

procs

地址:https://github.com/dalance/procs

zoxide

地址:https://github.com/ajeetdsouza/zoxide

bat

地址:https://github.com/sharkdp/bat

rustlings

地址:https://github.com/rust-lang/rustlings

exa

地址:https://github.com/ogham/exa

hyperfine

地址:https://github.com/sharkdp/hyperfine

bottom

地址:https://github.com/ClementTsang/bottom

下载 APP