1972:C 語言誕生——從 B 到 C 的跨跃#
事件#
1972 年,Dennis Ritchie 在 Bell Labs 於 PDP-11 上完成 C 語言的最初實作。C 由 Thompson 的 B 語言演化而來,B 又源自 Martin Richards 的 BCPL。1973 年,UNIX 核心以 C 改寫完成(約一萬行),證明高階語言足以勝任作業系統。
為何重要#
- 語言誕生:C 確立了系統程式語言的基本形態——指標、陣列、struct、函式、編譯式執行模型,成為其後半世紀絕大多數語言(C++、Objective-C、Java、C#、Go、Rust、JavaScript 的語法外觀)的共同祖先。
- 從 B 的「無型別」到 C 的「有型別」:這是本篇最重要的語法演進。
理論與實用原因(每個新語法為何加入)#
- 資料型別(
int、char、float): - 彌補的缺陷:B/BCPL 只有一種「機器字」(word)型別,指標與整數混用,編譯器無法檢查任何錯誤。
- 理論原因:型別系統(源自 Algol 68 與 CPL 的影響)讓編譯器能在編譯期捕捉型別錯誤。
- 實用原因:PDP-11 有多種字長的暫存器(byte、word、浮點),硬體提供不同指令,型別可直接映射到高效的機器碼——「型別即硬體」是 C 型別系統的核心思想。
- 指標(
*、&): - 彌補的缺陷:OS 核心必須操作記憶體位址(暫存器、裝置、頁表),B 的位址操作太粗糙。
- 實用原因:指標讓 C 既能寫高階邏輯,又能做組合語言才辦得到的位址運算;
*p的解參考語法沿自 BCPL/B。 - struct 與陣列:
- 彌補的缺陷:B 沒有結構化資料,描述檔案系統 inode、行程表等 OS 資料結構十分痛苦。
- 理論原因:呼應 Dijkstra「結構化程式設計」浪潮——資料也該結構化。
- 函式與遞迴:沿自 ALGOL 傳統,讓大型系統可以模組化分解。
彌補了什麼缺陷#
C 彌補了 B 語言「無型別、無結構化資料、編譯器無法驗證、效率不足」的缺陷,同時保住了組合語言「貼近硬體、零額外開銷」的優點——「信任程式設計師、不隱藏機器」是 C 的設計哲學。
程式範例#
B 語言(無型別,一切是 machine word)的困境:
/* B 語言風格:指標與整數混用,編譯器無法檢查任何錯誤 */
auto p;
p = 0177350; /* 一個「位址」,只是一個數字,寫錯了也不知道 */
p = 'A' + 5; /* 想拿字元?也是同一種型別,錯誤默默發生 */
1972 年的 C 加入型別後,同樣的程式變得可檢查:
#include <stdio.h>
int main(void) {
int n = 42; /* int:映射 PDP-11 的 16-bit word 指令 */
char c = 'A'; /* char:映射 byte 指令 */
float x = 3.14f; /* float:映射浮點處理器指令 */
int *p = &n; /* 指標:& 取位址、* 解參考,型別明確 */
*p = *p + 1; /* 編譯器知道這是 int 運算,生成高效指令 */
printf("%d\n", n); /* 43 */
return 0;
}
struct 彌補了「OS 資料結構無法描述」的缺陷——UNIX 的 inode 就靠它:
struct inode { /* 1973 年改寫 UNIX 核心的真實資料結構概念 */
unsigned short i_mode; /* 檔案型別與權限 */
unsigned short i_uid; /* 擁有者 */
unsigned long i_size; /* 檔案大小 */
unsigned short i_addr[8];/* 資料區塊位址 */
};
struct inode *ip; /* OS 核心操作記憶體位址的必備能力 */
ip->i_size = 1024; /* 結構化存取,而非硬記憶體偏移量 */
遞迴函式(沿自 ALGOL 傳統)讓大型系統可模組化分解:
/* 目錄樹遍歷——UNIX 檔案系統的典型遞迴 */
long dirsize(struct inode *dir) {
long total = 0;
for (int i = 0; i < 8; i++)
if (is_dir(dir->i_addr[i])) /* 子目錄 */
total += dirsize(get_inode(dir->i_addr[i])); /* 遞迴 */
else
total += get_inode(dir->i_addr[i])->i_size;
return total;
}