地址空间与内存 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走一遍它告诉我们的三件事:
- 三个指针分别落在地址空间的三个区:代码(低)、堆(中)、栈(高)。
栈的地址
0x7fff...明显比另两个大得多——印证了「栈在顶端」的布局约定2。 - 你打印出来的每个地址都是虚拟地址。旁注的原文说得很直白: 「程序员能看到的任何地址都是虚拟地址」;只有操作系统和硬件 知道它真正躺在物理内存的哪里3。本库 01 章那个「两个程序分到 同一地址 0x200000 却互不干扰」的怪事,谜底就是这一层翻译。
- 错觉必须无感。程序应当完全不知道自己被虚拟化了—— 这叫透明(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 走、被改写 |
| 重复 free | free 同一个指针两次 | 未定义行为,分配库数据结构被搅乱 |
图说:六行表格是 C 程序员的真实职业风险清单,也是「为什么后来 的语言要发明垃圾回收」的全部动机17。书里一句反直觉的实话值得记住: 进程退出时操作系统会收走它所有的页,所以短命程序漏内存「不算出事」—— 但养成不 free 的习惯,写长驻服务那天就是灾难18。
4. 作者的判断与证据
书里给了证据的判断:
- 虚拟内存三目标与「隔离原则」在引言章已立,此处重申——保护被明说是 虚拟内存存在的理由之一,不只是性能工程8。
- 每类 malloc 错误都给了最小复现代码,且明说「这些代码编译器一声不吭」—— 编译通过 ≠ 正确,这是作者反复强调的治学态度13。
作者的判断(立场):
- 「希望本章让你对段错误产生好奇,有求知欲去读下文」——教学安排上, 故意把术语的兑现(为什么叫「段」错误)留到分段一章,这在本书是常态: 先欠着,后兑现。
5. 边界与局限
- 地址空间的三个区是简化模型;真实系统还有静态数据段、内存映射区 (mmap,共享库、大文件映射都在那里),本章没画进去。
- malloc 的实现一个字没讲——分配器怎么管理空闲块,是本库 08 章 「空闲空间管理」的主题;此处只管接口和错。
- 垃圾回收只说「自动管理很香」,没有任何实现介绍——这是操作系统的书, 不是语言实现的书(但对照阅读本库其他语言类拆解,正好补这块)。
6. 可带走的
- 你写程序能看见的所有地址都是虚拟地址;物理地址你永远不会直接看到。
- 地址空间 = 代码 + 堆 + 栈;堆栈两端对长是约定,不是定律。
- 虚拟内存三目标(透明/效率/保护)背下来——后文每个机制都对应其中一个。
- 「保护」不是性能问题,是可靠性问题:隔离让故障停在单个进程里。
- C 的内存错六大类,每类都「编译通过、可能运行正常」——测试通过 什么也证明不了。
- 有 GC 也会泄漏:泄漏的定义是「不再用却还持有引用」,跟谁来 free 无关。
7. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 分时共享的早期笨拙方案 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:52(搜「save all of its state」) |
| 多程序驻留逼出保护需求 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:65(搜「protection」) |
| 地址空间定义 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:99(搜「address space」) |
| 16KB 布局图 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:124(搜「code segment」) |
| 难题:如何虚拟化内存 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:166(搜「crux」) |
| 三目标:透明/效率/保护 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:188(搜「transparency」)与:195(搜「efficiency」)与:202(搜「protection」) |
| 隔离原则 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:215(搜「ISOLATION」) |
| va.c 输出 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:290(搜「0x1095afe50」) |
| 「你看见的地址全是虚拟的」 | 13. The Abstraction: Address Spaces | text/15-ch13-13-the-abstraction-address-spaces.txt:263(搜「EVERY ADDRESS」) |
| 栈内存由编译器管理 | 14.Interlude: Memory API | text/16-ch14-14-interlude-memory-api.txt:26(搜「compiler」) |
| malloc 接口 | 14.Interlude: Memory API | text/16-ch14-14-interlude-memory-api.txt:55(搜「malloc() call」) |
| free 不传大小,库自己记账 | 14.Interlude: Memory API | text/16-ch14-14-interlude-memory-api.txt:146(搜「must be tracked」) |
| 忘记分配 → 段错误 | 14.Interlude: Memory API | text/16-ch14-14-interlude-memory-api.txt:181(搜「segmentation fault」) |
| 分配不够 → 安全漏洞 | 14.Interlude: Memory API | text/16-ch14-14-interlude-memory-api.txt:367(搜「buffer overflow」) |
| 泄漏与 GC 的局限 | 14.Interlude: Memory API | text/16-ch14-14-interlude-memory-api.txt:227(搜「memory leak」) |
| 进程退出,内存全收回 | 14.Interlude: Memory API | text/16-ch14-14-interlude-memory-api.txt:254(搜「WHY NO MEMORY」) |
Footnotes
-
出处:「13. The Abstraction: Address Spaces」第 275–297 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:290,搜「0x1095afe50」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 113–156 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:124,搜「code segment」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 263–274 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:263,搜「EVERY ADDRESS YOU SEE」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 188–194 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:188,搜「transparency」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 146–153 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:150,搜「grow in opposite」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 155–156 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:155,搜「multiple threads」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 182–205 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:188,搜「transparency」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 215–231 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:215,搜「ISOLATION」)。 ↩ ↩2 -
出处:「13. The Abstraction: Address Spaces」第 222–224 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:223,搜「microkernels」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 50–59 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:52,搜「save all of its state」)。 ↩ -
出处:「13. The Abstraction: Address Spaces」第 60–66 段(
text/15-ch13-13-the-abstraction-address-spaces.txt:65,搜「protection」)。 ↩ -
出处:「14.Interlude: Memory API」第 137–146 段(
text/16-ch14-14-interlude-memory-api.txt:146,搜「must be tracked」)。 ↩ -
出处:「14.Interlude: Memory API」第 148–154 段(
text/16-ch14-14-interlude-memory-api.txt:152,搜「nary a peep」)。 ↩ ↩2 -
出处:「14.Interlude: Memory API」第 163–191 段(
text/16-ch14-14-interlude-memory-api.txt:181,搜「segmentation fault」)。 ↩ -
出处:「14.Interlude: Memory API」第 193–217 段(
text/16-ch14-14-interlude-memory-api.txt:367,搜「buffer overflow」)。 ↩ -
出处:「14.Interlude: Memory API」第 226–233 段(
text/16-ch14-14-interlude-memory-api.txt:227,搜「memory leak」)。 ↩ -
出处:「14.Interlude: Memory API」第 155–161 段(
text/16-ch14-14-interlude-memory-api.txt:156,搜「newer languages」)。 ↩ -
出处:「14.Interlude: Memory API」第 254–276 段(
text/16-ch14-14-interlude-memory-api.txt:254,搜「WHY NO MEMORY IS LEAKED」)。 ↩