用餐厅和盘子理解栈和堆

简介: 最近在学 Rust。其实之前就学过一段时间,但学到所有权这里就卡住了,概念一多加上当时在专升本,后面就放弃了🥲。趁着国庆假期有整块时间,决定重新捡起来,这次争取把它啃下来。本文的内容来源于 Rust 官方教程 [What Is Ownership?](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html),在学习所有权(R

最近在学 Rust。其实之前就学过一段时间,但学到所有权这里就卡住了,概念一多加上当时在专升本,后面就放弃了🥲。趁着国庆假期有整块时间,决定重新捡起来,这次争取把它啃下来。

本文的内容来源于 Rust 官方教程 What Is Ownership?,在学习所有权(Rust 独有的机制)之前,官方先花了一段篇幅介绍栈(Stack)和堆(Heap)。很多从 Java、Python、Go 这类有 GC 的语言转过来的同学(包括我自己)平时很少关心数据到底放在哪里,所以这一段读起来有点抽象,我让 AI 翻译了一下并且加上了一些内容,方便我理解,我感觉写的挺好的,就分享一下。

栈 Stack

栈的规则只有一条,叫后进先出(LIFO):最后放上去的盘子,最先被拿走。

   放入(push)     取出(pop)
       ↓             ↑
    ┌───────┐
    │ 盘子 3 │  ← 栈顶:只能在这里操作
    ├───────┤
    │ 盘子 2 │
    ├───────┤
    │ 盘子 1 │  ← 最先放的,最后才拿走
    └───────┘

栈有一个硬性要求:每个数据的大小必须在编译时就确定,而且不能变。

就像每个盘子尺寸都一样,才能整整齐齐摞起来。i32(4 字节)、bool(1 字节)、[u8; 10](10 字节)这类类型都可以放在栈上。

栈为什么快? 因为根本不用找位置,新数据永远放在栈顶。分配内存只是把栈顶指针挪一下,几乎不花时间。

堆 Heap

堆:餐厅找座位

如果数据大小在编译时不知道,或者运行中会变化(比如用户输入的字符串、一个会不断 push 的 Vec),就没法放在栈上,只能放到堆里。

堆就像一家餐厅:

  1. 你进门告诉服务员:"我们 4 个人。"(申请 4 个单位的空间)
  2. 服务员在店里找一张够大的空桌。(分配器搜索空闲内存)
  3. 把桌子标记为"已占用",并告诉你:"你们坐 7 号桌。"(返回一个指针,也就是内存地址)
  4. 朋友晚到了,只要问"7 号桌在哪"就能找到你们。(通过指针访问数据)

栈和堆的内存结构

        栈(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 缓存命中率越高,处理就越快;在内存里跳来跳去就会变慢。

至于"释放时机"这一行的差别,正是后面要讲的重点:栈上的数据随函数调用自动清理,而堆上的数据需要有人管,这也是所有权要解决的问题。

函数调用的实现

调用函数时,参数和局部变量会被压入栈;函数返回时,这些数据被弹出,自动清理,不用操心。

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 开销,也不会忘记释放。
{
   
    let s = String::from("hello"); // 在堆上分配,s 是主人
    // 使用 s ...
}   // s 离开作用域 → Rust 自动调用 drop → 堆内存被释放
目录
相关文章
|
3月前
|
机器学习/深度学习 缓存 人工智能
月之暗面 Kimi K3 接入百炼平台:100 万 Token 长文本,缓存仅 2 元 / 百万输入
全球首个开源3万亿级大模型Kimi K3(2.8万亿参数)正式上线阿里云百炼平台,支持100万Token超长上下文、原生视觉理解与深度推理。文本生成、多模态分析、复杂逻辑任务表现卓越,输入20元/百万Token(缓存命中仅2元),面向长程编程、知识工作等高阶场景。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
353 3
|
1天前
|
缓存 开发工具 git
DSH skill 怎么安装、放在哪个目录?DeepSeek Harness 技能与插件的区别与用法
dsh skill 不是插件:它是给模型的可加载 Markdown 指令集,由本地提供方按 rank 扫描六个根目录发现,用 dsh plugin add 装不进去。本文讲清六个根目录的优先级、SKILL.md 写法与两个调用开关。
DSH skill 怎么安装、放在哪个目录?DeepSeek Harness 技能与插件的区别与用法
|
5天前
|
存储 NoSQL 安全
用 Java 接入大模型(五):多轮对话的上下文怎么管
本文深入剖析多轮对话的核心误区:大模型本身无记忆,所谓“遗忘”实为上下文管理失当。从messages结构化封装、token精准截断策略,到Redis会话存储与三步排错法,系统讲解如何可靠构建长程对话能力。
76 7
|
2天前
|
关系型数据库 MySQL 分布式数据库
阿里云数据库RDS:创建、配置白名单、连接数据库及核心功能说明
本文以阿里云PolarDB与RDS为例,详解云数据库从购买创建、安全配置(白名单/账号/TDE)、连接使用到核心功能(弹性扩展、高可用、数据安全、备份恢复、迁移)的全流程操作,助力用户快速上手主流云原生数据库。
|
21天前
|
边缘计算 运维 网络协议
AiWCLOUD:高防CDN,不限内容,大陆节点免备CDN技术底层拆解下
本文深度解析高防CDN技术链路:从L3-L7四层纵深防御(指纹匹配、TCP代握手、人机识别)、智能调度系统,到边缘计算下沉与HTTP/3、TLS1.3等协议优化,涵盖可观测性及选型评估维度,并展望eBPF、AI检测与零信任融合趋势。(239字)
106 2
|
17天前
|
人工智能 中间件 API
LangChain+Llama.cpp 本地模型与Agent工具调用
本文介绍如何用LangChain对接本地llama.cpp服务:通过`llama-server`启动Qwen量化模型,配置OpenAI兼容接口;安装LangChain生态包;实现基础对话与自定义工具Agent(如查天气、时间),全程离线运行,零依赖云端API,适合私有化AI原型开发。(239字)
153 3
|
21天前
|
人工智能 API 开发者
DeepSeek V4.1‑Flash内测完整实战:接口调用、工具接入、踩坑排错与代码示例全指南
新一代MoE架构模型DeepSeek V4.1‑Flash开启限时内测,该版本采用全新模型架构,原生具备多模态图文理解能力,推理生成速度大幅提升,同时沿用原有V4‑Flash计费标准,让开发者可以抢先体验迭代后的模型能力。本次内测属于限时中间版本,使用专门的临时模型标识符`deepseek‑v4.1‑flash‑expires‑on‑0910`,接口基础地址保持不变,不需要重新申请API密钥,只需要修改请求内部model字段就可以完成调用。
412 3
|
24天前
|
人工智能 数据挖掘 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8基座,六大核心能力重构企业自动化工作流与计费选型全指南
千问办公QwenWork依托Qwen3.8基座,通过六大核心能力打通文档处理、数据分析、浏览器自动化、自定义技能编排、多端Agent协同、企业业务系统,把AI能力从简单对话升级为完整任务交付,解决企业大量重复办公工作。但AI办公智能体属于效率辅助工具,无法完全替代业务人员专业判断,财务、法务类关键业务输出,必须经过人工审核校验。
311 2
|
2月前
|
人工智能 自然语言处理 安全
阿里云Qoder CN能做什么?只是代码补全还是能做更多?
阿里云Qoder CN(原灵码)是覆盖开发与办公全场景的AI智能体系列,含IDE插件、桌面助手、CLI终端、数字员工及云端Agent等形态,支持自主编程、专家协作、语音交互、安全扫描及跨端任务管理,助力高效智能工作。阿里云Qoder CN官网:https://t.aliyun.com/U/hGZKNX
|
2月前
|
关系型数据库 MySQL Linux
Linux 安装 MySQL 8 数据库(图文详细教程)
本教程手把手教你如何在 Linux 安装 MySQL 数据库。图文详细教程!
252 3
Linux 安装 MySQL 8 数据库(图文详细教程)