第四十四章:程序在内存里的样子
复习
- 函数调用用运行栈保存返回地址和局部数据。
- 指令与数据归根到底都需要放进内存。
TL;DR
- 一个运行中的程序通常把内存分为代码、静态数据、堆和栈等区域。
- 不同区域保存不同寿命和用途的数据。
- 这张地图是理解程序为何会崩溃、为何会耗尽内存的起点。
正文
从硬件的角度,内存就是一长排字节地址。可程序若把指令、全局数据、临时变量和后来申请的数据随意混放,很难管理。实际运行时,系统通常会给一个程序安排一张大致如下的地图:
较低地址
┌──────────────┐
│ 代码:机器指令 │
├──────────────┤
│ 静态数据 │ 例如程序开始前就有的常量、全局变量
├──────────────┤
│ 堆 │ 程序运行时按需要取得的空间
│ ↑ │
│ ↓ │
│ 栈 │ 函数调用的栈帧
└──────────────┘
较高地址
代码区保存翻译后的指令,通常不应被普通数据写坏。静态数据区保存生命期与整个程序一样长的数据。上一章的栈适合函数进入时创建、返回时消失的局部变量。
还有一些数据直到运行中才知道需要多少,例如用户打开的一张图片、动态增长的列表。这类空间常从堆中申请,使用完也需要在恰当时机归还,或由语言的运行环境代为回收。堆和栈不是“更高级的内存”,只是同一块内存按不同规则使用。
这张图不是每台机器、每种语言都完全相同。有的系统有更多区域,有的语言隐藏了部分细节。但它提供了有用的提问方式:一个变量活多久?谁负责它的空间?它会和哪类数据相邻?许多内存错误都和这些问题有关。
我们已能想象程序运行时占据的样子。可从编辑器里的源代码,怎样得到这份含有机器指令的程序?下一章沿着翻译工具把这条生产线走一遍。
思考题
函数返回后,为什么它的局部变量通常不能再被可靠地使用?
小结
知识点
- 代码、静态数据、堆和栈是常见的内存使用区域。
- 栈帧的生命期通常与一次函数调用相同。
- 堆适合运行时才确定大小或寿命的数据。
参考资料
- Randal Bryant、David O’Hallaron,《深入理解计算机系统》
思考题答案(仅供参考)
函数返回会撤掉它的栈帧,原先局部变量所占的位置可以被后续调用复用。即使旧的位暂时还在,也不能再把它当作有效变量。
协议
本文采用 CC BY-NC-SA 4.0 协议发布。