跳轉到

指標與記憶體位址對映:C 的型態系統#

信賴程式員:一種設計立場#

C 的型態系統背後有一句著名的設計立場:trust the programmer(信賴程式員)。Ritchie 在 1975 年的文件中直言,C 語言「不以協助程式設計師為主要目的」——它不阻止你做壞事,只提供你做事的槓桿。C 幾乎不做執行期檢查:陣列越界不報錯、指標可以任意算術、型態可以強制轉換。這不是疏忽,而是取捨:UNIX 的開發者是貝爾實驗室的世界級人才,他們要的是不被檢查機制拖累的速度與自由。

這與 ALGOL 系語言形成鮮明對比。Pascal 的設計者 Niklaus Wirth 明確主張語言應該防止錯誤:強型態、範圍檢查、嚴格的指標限制。兩條路線的哲學分歧延續至今——今天的 Rust 與 Java 是 Pascal 理念的遠代子孫,C 與其追隨者則堅守「程式員是成年人」的陣營。歷史對這場辯論的評價相當微妙:C 贏得了市場,卻也讓世界付出了數十年的安全代價(這是第 7 章的主題)。

指標即位址:算術的美學#

C 最具特色的概念是指標=位址的等式。在 C 裡,指標就是一個存放記憶體位址的變數,而對指標做算術有明確語義:如果 p 指向 int 陣列的元素,p + 1 不是位址加 1 個位元組,而是指向下一個 int——位移量由所指型態的大小決定。這個設計讓指標成為遍歷陣列的高效手段,編譯器可以把它編譯成 PDP-11 乾淨的間接定址指令。

C 更進一步規定陣列與指標的等價關係:a[i] 在語義上完全等同 *(a + i)。陣列名在表達式中退化為指向首元素的指標。這條規則讓 C 的陣列沒有邊界資訊(因此也無從檢查),卻換來了極致的靈活性:字串就是字元陣列,字元陣列就是指標,指標就是記憶體位址。整個語言的記憶體模型簡化成一句話——位址可以計算。

型態服務硬體:int 的隨機性#

C 的基本型態尺寸刻意不固定:int 是「宿主機器最自然的字組大小」,PDP-11 上是 16 位元,Interdata 8/32 上是 32 位元;char 是能容納字元集的最小單位;long 是最長的整數。struct 的成員對齊(alignment)也交由編譯器按硬體最佳化決定。這些「不確定性」在學術語言的標準看來不可思議,卻是刻意的:C 的每一份實作都應該生成對那台機器而言最有效率的程式碼。

代價是可攜性陷阱。一個在 PDP-11 上正確的程式,搬到 32 位元機器可能因 int 變寬而溢出;struct 的記憶體布局改變,會讓依賴二進位格式的程式崩潰。C 程式員於是養成了在型態尺寸上打轉的文化——sizeof、typedef、日後的 int32_t,都是這個歷史傷口的疤痕。

可攜性的證明:Interdata 8/32#

C 型態系統的終極考驗,是 1976 到 1979 年間的一次壯舉:把 UNIX 從 PDP-11 移植到字組結構截然不同的 Interdata 8/32(32 位元、位元組定址、大端序)。Thompson 與 Ritchie 帶著 tape 遠赴伊利諾大學的機房進行移植。這次經驗暴露了大量隱含 PDP-11 假設的程式碼,團隊逐一清理,最終證明了 UNIX 真的是「可攜的作業系統」,C 是「可攜的組語」。

此役的制度性後果遠超技術本身。移植版本(後來演成 UNIX/32V)成為加州大學柏克萊分校 BSD UNIX 的起點之一;而「高階語言寫核心+少許組語」的模式,成為此後所有作業系統的標準配方。微軟、Sun、SGI 的作業系統陸續改用 C,語言的信條之戰就此定局。

指標文化的雙面性#

指標是效率的神器,也是無數漏洞的根源。緩衝區溢位(buffer overflow)、懸空指標(dangling pointer)、野指標——這些 C 的原罪,日後成為蠕蟲與攻擊程式的溫床。1988 年的 Morris 蠕蟲正是利用 fingerd 的緩衝區溢位橫掃網際網路;21 世紀的 CVE 資料庫裡,記憶體安全類漏洞長年高居首位。這筆技術債將在第 7 章詳細清算。

C 占領世界的制度原因#

C 的勝利不只靠技術優勢,更靠三個制度條件。第一,貝爾實驗室的授權文化:UNIX 附原始碼授權給大學時,C 編譯器一併附贈,等於免費發放了一套工業級的系統語言工具鏈。第二,課程體制:1970 年代末起,C 取代組語與 Fortran 成為系統程式課程的標準語言,K&R 那本薄薄的書成為一代人的教科書。第三,生態鎖定:UNIX 的核心與工具都是 C,學 C 才能玩 UNIX,玩 UNIX 必然學 C——語言與系統互為載體,滾雪球式擴張。

1973 年之前,系統程式設計師的主力是組語;1978 年之後,是 C。語言史的斷代,就這樣劃在一個貝爾實驗室工程師為自己的機器造工具的決定上。