编程语言研究书库

地址空间与内存 API — 私有内存的错觉,与 malloc 的手艺

06 地址空间与内存 API — 私有内存的错觉,与 malloc 的手艺

1. 这一章讲什么

CPU 虚拟化解决「同时跑」,接下来解决「各有自己的内存」。 本章立起第二个大抽象:地址空间——每个进程看见的都是一套 从 0 开始的、私有的、可能很大的地址,尽管物理内存只有一块、 大家都在用。后半章落到手边:UNIX 的内存分配接口 malloc()/free() 怎么用、会犯哪些错——这些错正是后面所有内存管理机制要收拾的摊子。

在全书链条里的位置:内存虚拟化部分的地基。后面六章(基址/界限、 分段、分页……)都是为兑现本章承诺的三个目标而打的补丁。

2. 顶层全景

  每个进程看见的地址空间(虚拟的、私有的、连续的):
    0 ──▶ 代码(静态,放定就不动)
    1K ──▶ 堆 ←── malloc 申请,向下长
    ……(大片空闲,两边慢慢挤)……
    15K ──▶ 栈 ←── 函数调用,向上长
    16K(顶)
            │
  物理内存只有一块,三个进程 A/B/C 各占几段;操作系统保证:
    进程 A 访问「地址 0」时,实际落到分给 A 的那段物理内存

图说:左边是每个程序的「错觉世界」,右边是真相。中间的翻译工作, 就是本库 07–09 章的全部内容。

3. 核心原理

3.1 主线例子:一个打印指针的小程序,拆穿整个错觉

书里给了一个九行小程序 va.c,在 64 位 Mac 上的真实输出是1:

location of code : 0x1095afe50
location of heap : 0x1096008c0
location of stack: 0x7fff691aea64

走一遍它告诉我们的三件事:

  1. 三个指针分别落在地址空间的三个区:代码(低)、堆(中)、栈(高)。 栈的地址 0x7fff... 明显比另两个大得多——印证了「栈在顶端」的布局约定2。
  2. 你打印出来的每个地址都是虚拟地址。旁注的原文说得很直白: 「程序员能看到的任何地址都是虚拟地址」;只有操作系统和硬件 知道它真正躺在物理内存的哪里3。本库 01 章那个「两个程序分到 同一地址 0x200000 却互不干扰」的怪事,谜底就是这一层翻译。
  3. 错觉必须无感。程序应当完全不知道自己被虚拟化了—— 这叫透明(transparency),是虚拟内存的第一目标4。

3.2 地址空间的布局约定:两端对长

书里那个 16KB 的迷你地址空间(图 13.3)把布局讲得很死: 代码固定在最低处(它不变大,好安置);堆和栈是仅有的两个会伸缩的区, 被刻意放在两端、背向生长——堆从 1KB 往下长,栈从 16KB 往上长, 中间的空闲留给两边用5。为什么必须这样?因为只有对向生长, 两个动态区才都能扩张到占满全部空闲。书里也留了伏笔: 多个线程共享一个地址空间时,这套「两端对长」的优雅布局就玩不转了6 ——那是本库 11 章的事。

3.3 三个目标,一个矛盾核心

虚拟内存要同时做到7:

目标含义后面的哪章兑现
透明程序感觉不到虚拟化所有机制共同
效率时间和空间都不能贵TLB(08 章)、多级页表(09 章)
保护进程互不可碰,OS 也碰不得基址/界限(07 章)、页表权限位(08 章)

图说:三个目标互相拉扯——保护要检查(慢),效率要裸奔(危险)。 内存部分后面所有章节,都是这对矛盾的具体解法。

保护换来的是隔离(isolation):进程活在各自的「茧」里, 一个崩了不殃及旁人,也殃及不到操作系统本身8。这条原则后来被 微内核推到极端:把操作系统自己内部也切小块互相隔离9。

3.4 从分时史到地址空间:为什么必须翻译

一个容易忽略的史实:最早实现分时共享的办法很笨—— 把 A 的全部内存(不只寄存器)存到磁盘,再把 B 的全部内存读回来10。 内存一大这慢得没法用,于是改为「进程常驻内存、切换只换寄存器」—— 但多个程序同时驻留,立刻逼出两个问题:谁在内存的哪一段? **怎么防止 A 摸到 B?**地址空间和地址翻译就是这两个问题的答案11。

3.5 malloc/free 与七宗错

malloc(大小) 要么给指针要么给 NULL;free(指针) 归还, 大小不用你报——分配库自己记账12。栈内存由编译器自动管, 堆内存全交给你,所以错全出在堆上。书里列的错(全部「编译通过、 甚至运行正确、其实是错的」)13:

错代码形状后果
忘记分配strcpy(dst, src),dst 是没分配的野指针段错误(segmentation fault)——「内存惹怒了系统」14
分配不够malloc(strlen(src)),忘了 +1 留给结尾符缓冲区溢出,常常是安全漏洞的源头15
忘记初始化malloc 了不填值就读读到堆里的随机旧值
忘记 free长驻程序慢慢漏光内存最终重启;有垃圾回收的语言也救不了「还拿着引用」的漏16
提前 free释放后还用(悬垂指针)崩,或那块内存已被别人 malloc 走、被改写
重复 freefree 同一个指针两次未定义行为,分配库数据结构被搅乱

图说:六行表格是 C 程序员的真实职业风险清单,也是「为什么后来 的语言要发明垃圾回收」的全部动机17。书里一句反直觉的实话值得记住: 进程退出时操作系统会收走它所有的页,所以短命程序漏内存「不算出事」—— 但养成不 free 的习惯,写长驻服务那天就是灾难18。

4. 作者的判断与证据

书里给了证据的判断:

  • 虚拟内存三目标与「隔离原则」在引言章已立,此处重申——保护被明说是 虚拟内存存在的理由之一,不只是性能工程8。
  • 每类 malloc 错误都给了最小复现代码,且明说「这些代码编译器一声不吭」—— 编译通过 ≠ 正确,这是作者反复强调的治学态度13。

作者的判断(立场):

  • 「希望本章让你对段错误产生好奇,有求知欲去读下文」——教学安排上, 故意把术语的兑现(为什么叫「段」错误)留到分段一章,这在本书是常态: 先欠着,后兑现。

5. 边界与局限

  • 地址空间的三个区是简化模型;真实系统还有静态数据段、内存映射区 (mmap,共享库、大文件映射都在那里),本章没画进去。
  • malloc 的实现一个字没讲——分配器怎么管理空闲块,是本库 08 章 「空闲空间管理」的主题;此处只管接口和错。
  • 垃圾回收只说「自动管理很香」,没有任何实现介绍——这是操作系统的书, 不是语言实现的书(但对照阅读本库其他语言类拆解,正好补这块)。

6. 可带走的

  1. 你写程序能看见的所有地址都是虚拟地址;物理地址你永远不会直接看到。
  2. 地址空间 = 代码 + 堆 + 栈;堆栈两端对长是约定,不是定律。
  3. 虚拟内存三目标(透明/效率/保护)背下来——后文每个机制都对应其中一个。
  4. 「保护」不是性能问题,是可靠性问题:隔离让故障停在单个进程里。
  5. C 的内存错六大类,每类都「编译通过、可能运行正常」——测试通过 什么也证明不了。
  6. 有 GC 也会泄漏:泄漏的定义是「不再用却还持有引用」,跟谁来 free 无关。

7. 原文地图

主题原书章原文位置
分时共享的早期笨拙方案13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:52(搜「save all of its state」)
多程序驻留逼出保护需求13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:65(搜「protection」)
地址空间定义13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:99(搜「address space」)
16KB 布局图13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:124(搜「code segment」)
难题:如何虚拟化内存13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:166(搜「crux」)
三目标:透明/效率/保护13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:188(搜「transparency」)与:195(搜「efficiency」)与:202(搜「protection」)
隔离原则13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:215(搜「ISOLATION」)
va.c 输出13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:290(搜「0x1095afe50」)
「你看见的地址全是虚拟的」13. The Abstraction: Address Spacestext/15-ch13-13-the-abstraction-address-spaces.txt:263(搜「EVERY ADDRESS」)
栈内存由编译器管理14.Interlude: Memory APItext/16-ch14-14-interlude-memory-api.txt:26(搜「compiler」)
malloc 接口14.Interlude: Memory APItext/16-ch14-14-interlude-memory-api.txt:55(搜「malloc() call」)
free 不传大小,库自己记账14.Interlude: Memory APItext/16-ch14-14-interlude-memory-api.txt:146(搜「must be tracked」)
忘记分配 → 段错误14.Interlude: Memory APItext/16-ch14-14-interlude-memory-api.txt:181(搜「segmentation fault」)
分配不够 → 安全漏洞14.Interlude: Memory APItext/16-ch14-14-interlude-memory-api.txt:367(搜「buffer overflow」)
泄漏与 GC 的局限14.Interlude: Memory APItext/16-ch14-14-interlude-memory-api.txt:227(搜「memory leak」)
进程退出,内存全收回14.Interlude: Memory APItext/16-ch14-14-interlude-memory-api.txt:254(搜「WHY NO MEMORY」)

Footnotes

  1. 出处:「13. The Abstraction: Address Spaces」第 275–297 段(text/15-ch13-13-the-abstraction-address-spaces.txt:290,搜「0x1095afe50」)。 ↩

  2. 出处:「13. The Abstraction: Address Spaces」第 113–156 段(text/15-ch13-13-the-abstraction-address-spaces.txt:124,搜「code segment」)。 ↩

  3. 出处:「13. The Abstraction: Address Spaces」第 263–274 段(text/15-ch13-13-the-abstraction-address-spaces.txt:263,搜「EVERY ADDRESS YOU SEE」)。 ↩

  4. 出处:「13. The Abstraction: Address Spaces」第 188–194 段(text/15-ch13-13-the-abstraction-address-spaces.txt:188,搜「transparency」)。 ↩

  5. 出处:「13. The Abstraction: Address Spaces」第 146–153 段(text/15-ch13-13-the-abstraction-address-spaces.txt:150,搜「grow in opposite」)。 ↩

  6. 出处:「13. The Abstraction: Address Spaces」第 155–156 段(text/15-ch13-13-the-abstraction-address-spaces.txt:155,搜「multiple threads」)。 ↩

  7. 出处:「13. The Abstraction: Address Spaces」第 182–205 段(text/15-ch13-13-the-abstraction-address-spaces.txt:188,搜「transparency」)。 ↩

  8. 出处:「13. The Abstraction: Address Spaces」第 215–231 段(text/15-ch13-13-the-abstraction-address-spaces.txt:215,搜「ISOLATION」)。 ↩ ↩2

  9. 出处:「13. The Abstraction: Address Spaces」第 222–224 段(text/15-ch13-13-the-abstraction-address-spaces.txt:223,搜「microkernels」)。 ↩

  10. 出处:「13. The Abstraction: Address Spaces」第 50–59 段(text/15-ch13-13-the-abstraction-address-spaces.txt:52,搜「save all of its state」)。 ↩

  11. 出处:「13. The Abstraction: Address Spaces」第 60–66 段(text/15-ch13-13-the-abstraction-address-spaces.txt:65,搜「protection」)。 ↩

  12. 出处:「14.Interlude: Memory API」第 137–146 段(text/16-ch14-14-interlude-memory-api.txt:146,搜「must be tracked」)。 ↩

  13. 出处:「14.Interlude: Memory API」第 148–154 段(text/16-ch14-14-interlude-memory-api.txt:152,搜「nary a peep」)。 ↩ ↩2

  14. 出处:「14.Interlude: Memory API」第 163–191 段(text/16-ch14-14-interlude-memory-api.txt:181,搜「segmentation fault」)。 ↩

  15. 出处:「14.Interlude: Memory API」第 193–217 段(text/16-ch14-14-interlude-memory-api.txt:367,搜「buffer overflow」)。 ↩

  16. 出处:「14.Interlude: Memory API」第 226–233 段(text/16-ch14-14-interlude-memory-api.txt:227,搜「memory leak」)。 ↩

  17. 出处:「14.Interlude: Memory API」第 155–161 段(text/16-ch14-14-interlude-memory-api.txt:156,搜「newer languages」)。 ↩

  18. 出处:「14.Interlude: Memory API」第 254–276 段(text/16-ch14-14-interlude-memory-api.txt:254,搜「WHY NO MEMORY IS LEAKED」)。 ↩

On this page