Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

第四十四章:程序在内存里的样子

复习

  • 函数调用用运行栈保存返回地址和局部数据。
  • 指令与数据归根到底都需要放进内存。

TL;DR

  • 一个运行中的程序通常把内存分为代码、静态数据、堆和栈等区域。
  • 不同区域保存不同寿命和用途的数据。
  • 这张地图是理解程序为何会崩溃、为何会耗尽内存的起点。

正文

  从硬件的角度,内存就是一长排字节地址。可程序若把指令、全局数据、临时变量和后来申请的数据随意混放,很难管理。实际运行时,系统通常会给一个程序安排一张大致如下的地图:

较低地址
┌──────────────┐
│ 代码:机器指令 │
├──────────────┤
│ 静态数据       │  例如程序开始前就有的常量、全局变量
├──────────────┤
│ 堆             │  程序运行时按需要取得的空间
│      ↑         │
│      ↓         │
│ 栈             │  函数调用的栈帧
└──────────────┘
较高地址

  代码区保存翻译后的指令,通常不应被普通数据写坏。静态数据区保存生命期与整个程序一样长的数据。上一章的适合函数进入时创建、返回时消失的局部变量。

  还有一些数据直到运行中才知道需要多少,例如用户打开的一张图片、动态增长的列表。这类空间常从中申请,使用完也需要在恰当时机归还,或由语言的运行环境代为回收。堆和栈不是“更高级的内存”,只是同一块内存按不同规则使用。

  这张图不是每台机器、每种语言都完全相同。有的系统有更多区域,有的语言隐藏了部分细节。但它提供了有用的提问方式:一个变量活多久?谁负责它的空间?它会和哪类数据相邻?许多内存错误都和这些问题有关。

  我们已能想象程序运行时占据的样子。可从编辑器里的源代码,怎样得到这份含有机器指令的程序?下一章沿着翻译工具把这条生产线走一遍。

思考题

函数返回后,为什么它的局部变量通常不能再被可靠地使用?

小结

知识点

  • 代码、静态数据、堆和栈是常见的内存使用区域。
  • 栈帧的生命期通常与一次函数调用相同。
  • 堆适合运行时才确定大小或寿命的数据。

参考资料

  • Randal Bryant、David O’Hallaron,《深入理解计算机系统》

思考题答案(仅供参考)

  函数返回会撤掉它的栈帧,原先局部变量所占的位置可以被后续调用复用。即使旧的位暂时还在,也不能再把它当作有效变量。

协议

本文采用 CC BY-NC-SA 4.0 协议发布。