9.1 靠近硬體的抽象:指標與記憶體地址的精準操控#
C 的誕生現場:貝爾實驗室的 1969–1973#
故事從 UNIX 開始。Ken Thompson 1969 年在貝爾實驗室的廢棄 PDP-7 上(實驗室不給他買機器跑自己的遊戲 Space Travel——微觀政治與閒置資源催生大發明)寫出了 UNIX 雛形,用 B 語言。1971 年實驗室購入 PDP-11,移植需求暴露了 B 的短板:無型別、字組長度固定。
Dennis Ritchie(1941–2011)在 1971–1973 年間把 B 改造成 NB(New B)再改名 C:回歸型別(字元、整數、浮點、陣列、結構),並把 BCPL/B 的「向量」升級為指標 + 位址算術。1973 年,UNIX 核心用 C 重寫——作業系統第一次用高階語言寫成(此前系統軟體都是組語的禁臠)。這是一場靜默革命:它證明高階語言可以進入系統層,從此「語言」與「硬體」之間不再有不可逾越的組語護城河。
指標:C 的靈魂與詛咒#
指標的本質一句話:變數的值是另一個變數的位址。
int x = 42;
int *p = &x; /* p 存放 x 的位址 */
*p = 7; /* 透過位址改寫 x */
指標使 C 獲得了三種力量:
- 資料結構的自由:鏈表、樹、圖——動態結構全靠指標穿針引線
- 傳址語義:函數可以修改呼叫者的變數
- 零成本的抽象:陣列
a[i]編譯為*(a + i)——語法糖之下就是位址算術
「指標即陣列即位址」的統一,讓 C 成為記憶體的透明視窗:你能寫的每一行代碼,都能在心裡展開為機器將要做的每一件事。這種「無欺騙性」是 C 哲學的核心(9.3)。
位址算術的優雅與深淵#
C 的指標支援算術:p + 1 前進一個元素(不是一個位元組——編譯器按型別大小縮放)。但同一機制在無人看管時就是深淵:
| 災難模式 | 後果 |
|---|---|
| 越界存取(buffer overflow) | 1988 年 Morris 蠕蟲藉 fingerd 的溢出感染全球 6 千台機器,第一起網路安全事件 |
| 懸空指標(use-after-free) | 漏洞利用的頭號素材 |
| 空指標解引用 | 程式崩潰(segfault) |
| 型別混淆 | 未定義行為的迷宮 |
未定義行為(UB)是 C 標準的深層設計:標準不規定「錯誤的程式」會發生什麼——把檢查成本從編譯器移給程式員。在 1970s 這是合理的經濟學(每次越界檢查都是寶貴的機器週期);在 2020s 它是安全災難的溫床(主流漏洞清單中記憶體安全類長期過半,微軟 2019、Google 2020 的統計都指向 70% 級別)。一個時代的合理妥協,成為另一個時代的系統性風險——這是技術債的教科書定義。
指標的教育史:入門之牆#
「指標」幾十年來是程式教學的第一道鬼門關。兩種教學哲學:
- C 先行派:先受苦再抽象(K&R《C 程式設計語言》1978 的傳統,該書以「薄、準、美」著稱)
- 抽象先行派:先用 Python/Scratch 建立信心,指標延後(2000s 起的主流)
歷史曲線有趣:2000s 抽象派獲勝,但 2010s 安全危機又把指標(的觀念)請回課堂——Rust 甚至用型別系統來教指標語義(借用檢查器是「指標的教育學」的最高形態,15.2)。
指標的現代變奏#
- 引用(reference):C++/Java/Python 的受控指標——把危險面封裝起來
- 智慧指標:C++ 的
unique_ptr/shared_ptr——用 RAII(11.2)自動管理 - 借用與所有權:Rust 把指標規則寫進型別系統——編譯器成為指標紀律的執法者
- 理論本源:程式的記憶體模型 = 有向圖,「指標 = 圖的邊」;GC(7.3)= 可達性
小結#
C 用「指標 + 型別」在 1972 年畫出了一條至今有效的線:
距離硬體夠近(可寫作業系統),距離人類夠遠(不必寫組語)。
下一節看 C 與 UNIX 的共生如何塑造了整個軟體生態。