Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

程序在内存里的样子

复习

  • 分支与循环:高级语言结构如何落到比较和跳转指令
  • 函数:把一段工作命名、传入参数并获得结果
  • 函数调用与运行栈:保存参数、局部变量和返回地址

TL;DR

  • 程序在内存里大致分成几块:代码区、数据区、堆、栈
  • 代码区放指令,数据区放全局数据,栈放函数调用,堆放运行时动态申请的内存
  • 不同的块职责不同、权限不同,增长方向也不同
  • 搞清“变量住在哪”,很多现象就说得通了

正文

  我们一直在用内存:放指令、放数据、放栈帧。可它到底是怎么安排这些内容,才能各就各位、互不打架?这一章,把程序在内存里的全貌铺开来看。

一张内存地图

  一个正在运行的程序,内存大致长这样:

高地址  ┌───────────────┐
        │      栈        │  ← 向下增长
        │       ↓        │
        │               │
        │       ↑        │
        │      堆        │  ← 向上增长
        ├───────────────┤
        │     数据区      │  全局变量、常量
        ├───────────────┤
        │     代码区      │  指令(通常只读)
低地址  └───────────────┘

  一块一块看。

代码区

  代码区放的是 指令 ,也就是编译好后的一串机器码。从“第一次呼吸”起,PC 就是在这片区域里指来指去的。

  它通常被设为 只读 :程序运行时不应该去改自己的指令。这也是一道安全防线——万一有人想篡改代码,硬件直接拦下。

数据区

  数据区放 全局数据 :在函数外定义的变量、写死的常量等。它们在程序一开始运行时就分配好,直到程序结束才释放。

  因为地址在加载时就定了,全局变量的位置比较“稳定”,但也因此一直占着内存。

栈:自动来去

  栈,上一章刚讲过,专门伺候函数调用。参数、返回地址、局部变量都放在这里。

  它有两个特点:

  • 自动 :函数一调用就分配,一返回就回收,不用程序员操心
  • 向下增长 :新栈帧压在更低的地址上

  局部变量“出了函数就没了”,正是因为它的家——栈帧——在函数返回时被收走了。

堆:按需申请

  栈的空间有限,而且讲究“随调用来、随返回走”。可有些数据活得更久、更大,没法塞进栈里。比如“读入一个不确定长度的列表”,事先根本不知道该留多少地方。

  这时就用 (heap)。堆是一大片空闲区域,程序 在运行时按需申请 :需要一块内存,就用相应的方式向系统要;用完了,再还回去。它不随函数返回自动消失,什么时候释放,得由程序自己(或它的运行时)决定。

  堆向上增长,栈向下增长,两者迎面靠近——所以只要栈和堆撞上,就说明内存不够用了。

变量到底住在哪

  把前面这些串起来,就能回答一个常见问题:变量住在哪?

变量种类住在哪何时分配何时释放
全局变量数据区程序加载时程序结束时
局部变量函数调用时函数返回时
动态申请的内存运行时按需程序显式释放

  这张表能解释很多现象:为什么局部变量出了函数就访问不到了,为什么动态申请的内存忘记释放会越用越多,为什么递归太深会栈溢出。

  至此,一个“程序”的骨架就齐了:有指令、有数据、有栈、有堆。但我们还欠着一笔账——这个程序,究竟是怎么从我们写的那几行文字,变成内存里这副样子的?

思考题

  局部变量和动态申请的内存,一个放栈、一个放堆。它们各自适合保存什么样的数据?为什么“一个不知道长度的列表”更适合放堆,而不是栈?

小结

知识点

  • 程序在内存里分为代码区、数据区、堆、栈
  • 代码区放指令,通常只读
  • 数据区放全局数据
  • 栈保存函数调用,自动分配与回收,向下增长
  • 堆按需申请,向上增长,手动管理

参考资料

  1. Wikipedia(zh):内存布局:程序的内存分区
  2. Wikipedia(zh):栈 (数据结构):调用栈
  3. Wikipedia(zh):动态内存分配:堆与内存管理

思考题答案(仅供参考)

  栈适合“随函数调用而生、随函数返回而死”的小块数据,比如参数和局部变量;堆适合大小不定、生命周期长、需要跨函数存活的数据。一个不知道长度的列表,事先不知道要留多大地方,而且往往要在多个函数之间传递、活得更久,这些都更适合放到堆上按需分配。栈讲究来去分明,放这种数据就不合适了。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪