|
| 1 | +## 堆(heap)和栈(stack) |
| 2 | + |
| 3 | +``` |
| 4 | +那个“栈”经常会在各种文章里看到。 |
| 5 | +先不管专业定义,先看一下外行也能稍微理解一下的意思。 |
| 6 | +``` |
| 7 | + |
| 8 | +### 英文原本的意思 |
| 9 | + |
| 10 | +- stack |
| 11 | + - a stack of books → 一摞书 |
| 12 | + - a stack of plates → 一叠盘子 |
| 13 | + - a stack of papers → 一叠文件 |
| 14 | + - stacked chairs → 椅子叠在一起 |
| 15 | + |
| 16 | +- heap |
| 17 | + - a heap of clothes → 一堆衣服(乱丢在床上的那种) |
| 18 | + - a heap of trash → 一堆垃圾 |
| 19 | + - a heap of sand → 一堆沙子(倒在那里) |
| 20 | + - in a heap → 成一堆地摊着 |
| 21 | + |
| 22 | +stack和heap都是放东西,stack可以看到一层一层的,heap就是一堆团在一起。 |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +### 先用大厨做饭的例子理解一下 |
| 27 | + |
| 28 | +👩🍳[大厨](https://fivsevn.home.blog/foodie/)在家做饭,要用到【厨房台面】和【冰箱/储物柜】。 |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +> stack(栈):大厨手边的料理台工作区 |
| 33 | +
|
| 34 | +- 大厨要: |
| 35 | + - 一边切菜 |
| 36 | + - 一边放盘子 |
| 37 | + - 一边放调味料 |
| 38 | + - 一边丢临时用的餐巾纸啊什么的 |
| 39 | + - 做完一步马上清掉,下一步再放新东西 |
| 40 | + |
| 41 | +``` |
| 42 | +特点! |
| 43 | +· 用完即走 |
| 44 | +· 只放当前步骤要用的东西 |
| 45 | +· 顺序明确,越新的东西越放在“最上层” |
| 46 | +· 空间有限,挤满了就不好用了 |
| 47 | +· 任务结束,台面会自动恢复空白 |
| 48 | +``` |
| 49 | + |
| 50 | +- 大厨做完饭,stack(栈)就没了 |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +> heap(堆):大厨的冰箱、放干货的储物柜等等。 |
| 55 | +
|
| 56 | +- 大厨要: |
| 57 | + - 从冰箱拿肉肉 |
| 58 | + - 再放回去一部分 |
| 59 | + - 把昨天剩菜存进去 |
| 60 | + - 把买回来的蔬菜塞进储物柜 |
| 61 | + - 某些调料放进去放一个月都不动 |
| 62 | + |
| 63 | +``` |
| 64 | +特点! |
| 65 | +· 容量大、可以随便塞 |
| 66 | +· 用不用无所谓,可以放很久 |
| 67 | +· 不按顺序,随便放 |
| 68 | +· 要靠大厨手动整理,否则会越来越乱 |
| 69 | +· 不会自动清理,除非你开冰箱清空 |
| 70 | +``` |
| 71 | + |
| 72 | +- heap(堆)是个长期、无序、大容量的区域。 |
| 73 | + |
| 74 | +--- |
| 75 | +### 对照计算机术语 |
| 76 | + |
| 77 | +- `从冰箱取肉`|`从heap取数据` |
| 78 | +- `放到料理台`|`在stack上创建临时变量` |
| 79 | +- `切肉、炒肉`|`运行函数,使用stack` |
| 80 | +- `用完的剩下食材放回冰箱`|`把需要长期存活的对象保存在heap` |
| 81 | +- `做完饭把料理台清空`|`函数结束,stack自动清理` |
| 82 | +- `冰箱里还留着没用完的东西`|`heap的对象继续存在,等待 GC 或释放` |
| 83 | + |
| 84 | +-- |
| 85 | +### 让GPT给了几个具体的使用场景,随便熟悉一下 |
| 86 | + |
| 87 | +#### 一、Stack 在真实开发里的典型用法 |
| 88 | + |
| 89 | +例句 1:函数里放太多东西,stack 会爆 |
| 90 | + |
| 91 | +> “别在函数里定义 20MB 的数组,stack 会直接爆掉。” |
| 92 | +
|
| 93 | +场景: |
| 94 | +某人写了一个函数,里面搞了个巨大的临时数组,程序直接崩。 |
| 95 | + |
| 96 | +真实原因:stack 空间固定(比如只有 1MB),装不下。 |
| 97 | + |
| 98 | +⸻ |
| 99 | + |
| 100 | +例句 2:这个变量别放 stack 上,生命周期太短 |
| 101 | + |
| 102 | +> “你要返回这个对象,不能放 stack,它出了函数就没了。” |
| 103 | +
|
| 104 | +场景: |
| 105 | +新手把一个结构体声明在函数里,还 return 它的地址。 |
| 106 | +运行结果:指针指向的内存瞬间消失。 |
| 107 | + |
| 108 | +stack 变量 = 函数结束即消失。 |
| 109 | + |
| 110 | +⸻ |
| 111 | + |
| 112 | +例句 3:递归太深了,Stack Overflow(栈溢出) |
| 113 | + |
| 114 | +> “这递归 5000 层了,stack 顶不住。” |
| 115 | +
|
| 116 | +场景: |
| 117 | +深度递归每进入一层,就压一层 stack frame,最后爆掉。 |
| 118 | + |
| 119 | +你肯定听过 Stack Overflow(程序错误,不是那个网站),就是字面含义。 |
| 120 | + |
| 121 | +⸻ |
| 122 | + |
| 123 | +例句 4:这个函数调用链太长,stack 占用很大 |
| 124 | + |
| 125 | +> “这个算法层级太深,stack frame 撑满了,得改循环写法。” |
| 126 | +
|
| 127 | +场景: |
| 128 | +函数 A 调 B 调 C 调 D…… |
| 129 | +每一层都在 stack 上放一帧。 |
| 130 | + |
| 131 | +⸻ |
| 132 | + |
| 133 | +#### 二、Heap 在真实开发里的典型用法 |
| 134 | + |
| 135 | +例句 1:别忘了 free,不然 heap 会泄漏(内存泄漏) |
| 136 | + |
| 137 | +> “你 malloc 了不 free,这程序跑一小时 heap 都被你泄得差不多了。” |
| 138 | +
|
| 139 | +场景: |
| 140 | +常见 bug:申请 heap 空间但忘了释放。 |
| 141 | + |
| 142 | +heap 内存泄漏就像你冰箱里丢垃圾,不清会越来越多。 |
| 143 | + |
| 144 | +⸻ |
| 145 | + |
| 146 | +例句 2:这个对象很大,放 stack 不行,放 heap |
| 147 | + |
| 148 | +> “这个 10MB 的结构必须 new 分配,stack 放不下。” |
| 149 | +
|
| 150 | +场景: |
| 151 | +大对象必须放 heap,stack 会爆。 |
| 152 | + |
| 153 | +⸻ |
| 154 | + |
| 155 | +例句 3:GC(垃圾回收)在清 heap |
| 156 | + |
| 157 | +> “程序卡一下,是在做 heap 的垃圾回收。” |
| 158 | +
|
| 159 | +场景: |
| 160 | +Java / Go / Python 某次停顿: |
| 161 | +垃圾回收器正在整理 heap。 |
| 162 | + |
| 163 | +你能想象成:程序暂停一下清冰箱。 |
| 164 | + |
| 165 | +⸻ |
| 166 | + |
| 167 | +例句 4:Heap 碎片太严重了,得 compact 一下 |
| 168 | + |
| 169 | +> “堆碎片太多了,GC compact 一下才能挤出连续空间。” |
| 170 | +
|
| 171 | +场景: |
| 172 | +heap 会碎掉变成不连续的小块,导致申请大对象失败。 |
| 173 | + |
| 174 | +stack 永远不会碎片化,heap 会。 |
| 175 | + |
| 176 | +⸻ |
| 177 | + |
| 178 | +例句 5:这个对象生命周期不确定,用 heap 合适 |
| 179 | + |
| 180 | +> “这个状态要跨好几个函数,用 heap 存吧。” |
| 181 | +
|
| 182 | +场景: |
| 183 | +程序里长期存在的对象,比如用户会话、游戏角色。 |
| 184 | + |
| 185 | +⸻ |
| 186 | + |
| 187 | +#### 三、Stack 和 Heap 一起出现的真实语境 |
| 188 | + |
| 189 | +例句 1:stack 上放指针,heap 才放真实数据 |
| 190 | + |
| 191 | +> “别搞混了,你在 stack 上放的是指向 heap 的指针,不是对象本身。” |
| 192 | +
|
| 193 | +场景: |
| 194 | +常见误区: |
| 195 | +以为 new 出来的对象在 stack 上,实际上: |
| 196 | + • stack:存指针 |
| 197 | + • heap:存对象 |
| 198 | + |
| 199 | +⸻ |
| 200 | + |
| 201 | +例句 2:stack frame 分配很快,heap 分配要走 allocator |
| 202 | + |
| 203 | +> “这个函数很快,因为全是 stack 分配,没有 heap 分配。” |
| 204 | +
|
| 205 | +场景: |
| 206 | +性能优化时常说:stack 分配快如闪电,heap 慢得多。 |
| 207 | + |
| 208 | +⸻ |
| 209 | + |
| 210 | +例句 3:为了性能,减少 heap 分配,多用 stack |
| 211 | + |
| 212 | +> “GC 太慢了,把短期对象都放到 stack 上更快。” |
| 213 | +
|
| 214 | +场景: |
| 215 | +高性能服务器里,程序员会尽量减少 heap 分配。 |
| 216 | + |
| 217 | +⸻ |
| 218 | + |
| 219 | +例句 4:不要从 stack 返回地址,不然指向无效内存 |
| 220 | + |
| 221 | +> “你返回了一个 stack 地址,出了函数这个东西已经没了。” |
| 222 | +
|
| 223 | +场景: |
| 224 | +C/C++ 新手经典错误。 |
| 225 | + |
| 226 | +⸻ |
| 227 | + |
| 228 | +例句 5:这个对象生命周期不匹配,应该从 stack 改到 heap |
| 229 | + |
| 230 | +> “这个对象要跨函数用,别放 stack。” |
| 231 | +
|
| 232 | +场景: |
| 233 | +设计对象生命周期时的常见讨论。 |
| 234 | + |
| 235 | +⸻ |
| 236 | + |
| 237 | +#### 四、最典型的真实场景故事(完整体验 stack 和 heap) |
| 238 | + |
| 239 | +场景:处理用户请求(一个 Web 服务器) |
| 240 | + |
| 241 | +- 1)请求进来 |
| 242 | +程序开一个线程 / 协程 → 新建 stack |
| 243 | +像料理台一样准备好临时空间 |
| 244 | + |
| 245 | +- 2)解析用户输入 |
| 246 | +临时变量、局部字符串 → 放 stack |
| 247 | + |
| 248 | +- 3)要查询数据库,结果要返回给前端 |
| 249 | +数据结构 → 放 heap(因为需要长期存在一会儿) |
| 250 | + |
| 251 | +- 4)stack 上创建指向 heap 数据的指针 |
| 252 | +就像料理台放一张便签写“冰箱有鸡肉(heap 地址)” |
| 253 | + |
| 254 | +- 5)请求结束 |
| 255 | +stack 自动消失 |
| 256 | +heap 中的对象如果不再需要 → GC 或 free |
| 257 | + |
| 258 | +⸻ |
| 259 | + |
| 260 | +#### 五、拿【处理 Meshtastic / LoRa 节点上传的数据】举例 |
| 261 | + |
| 262 | +python |
| 263 | + |
| 264 | +```python |
| 265 | +def handle_message(node_id, raw_payload): |
| 266 | + # ------------- STACK(函数临时工作区)------------- |
| 267 | + # 这些变量存在于函数调用期间,用完就被丢弃 |
| 268 | + |
| 269 | + parsed = raw_payload.decode(”utf-8“) # 临时解析结果 → stack |
| 270 | + print(f”收到节点 {node_id} 的消息:{parsed}“) |
| 271 | + |
| 272 | + # 创建一个临时 dict(仍在 stack 上,短期变量) |
| 273 | + result = { |
| 274 | + ”node“: node_id, |
| 275 | + ”text“: parsed, |
| 276 | + } |
| 277 | + |
| 278 | + # 调用另一个函数,把 result 传下去 |
| 279 | + store_message(result) # result 会作为“值”传递,但引用活在 heap(见下) |
| 280 | + # --------------------------------------------------- |
| 281 | + |
| 282 | + |
| 283 | +def store_message(msg): |
| 284 | + # ------------- STACK(此函数自己的临时空间)------------- |
| 285 | + # msg 是一个引用变量,放在 stack |
| 286 | + # 但 msg 真实内容(dict)在 heap |
| 287 | + |
| 288 | + print(”正在保存消息...“) |
| 289 | + |
| 290 | + # 这个 append 会把 msg(字典对象)放入 heap 里的全局列表 |
| 291 | + MESSAGE_DB.append(msg) # 这里的 MESSAGE_DB 和里面的对象在 heap |
| 292 | + # --------------------------------------------------- |
| 293 | + |
| 294 | + |
| 295 | +# ------------- HEAP(进程长期存放数据的区域)------------- |
| 296 | +# 程序运行期间一直存在,只要你不关程序,这里就不会消失 |
| 297 | + |
| 298 | +MESSAGE_DB = [] # 全局列表 → heap,存活时间 = 程序整个生命周期 |
| 299 | + |
| 300 | +# ---------------------------------------------------------- |
| 301 | + |
| 302 | + |
| 303 | +# 模拟收到一条 LoRa / Meshtastic 数据 |
| 304 | +handle_message(”node_01“, b”Hello Mesh“) |
| 305 | +handle_message(”node_02“, b”GPS: 31.2304,121.4737“) |
| 306 | + |
| 307 | +print(”\n所有存档的消息:“) |
| 308 | +for m in MESSAGE_DB: |
| 309 | + print(m) |
| 310 | +``` |
| 311 | + |
| 312 | +运行结果: |
| 313 | + |
| 314 | +- 两次 handle_message 的临时变量全部消失(stack 自动清空) |
| 315 | +- 但 MESSAGE_DB 仍然保存消息在 heap(长期存活) |
| 316 | + |
| 317 | +```text |
| 318 | +收到节点 node_01 的消息:Hello Mesh |
| 319 | +正在保存消息... |
| 320 | +收到节点 node_02 的消息:GPS: 31.2304,121.4737 |
| 321 | +正在保存消息... |
| 322 | + |
| 323 | +所有存档的消息: |
| 324 | +{’node‘: ’node_01‘, ’text‘: ’Hello Mesh‘} |
| 325 | +{’node‘: ’node_02‘, ’text‘: ’GPS: 31.2304,121.4737‘} |
| 326 | +``` |
| 327 | + |
| 328 | +--- |
| 329 | +目前不一定要全部都看懂,但是可以理解一下意思。 |
| 330 | + |
| 331 | + |
| 332 | + |
| 333 | + |
0 commit comments