跳轉到

1972 — C 語言誕生#

案件摘要#

1972 年,Bell Labs 的 Dennis Ritchie 在 Ken Thompson 的 B 語言基礎上設計出 C 語言,並與 Thompson 聯手用 C 重寫 Unix。指標、struct 與可移植性的組合,讓 C 成為「作業系統的母語」——五十年後,幾乎所有現代作業系統內核仍以 C 寫成。

前因 -- 為什麼會有這個案子#

Unix 在 1969–71 年誕生後面臨一個致命瓶頸:它完全以 PDP-7/PDP-11 組合語言寫成,綁死在特定機器上——這正是 Multics 想避免卻被 Unix 繼承的老毛病。要讓 Unix 可移植,必須有一門「夠高階又不失硬體掌控力」的語言。

譜系的前因是這樣串起來的:

  1. ALGOL 60(歐洲,1960):學術語言,引入了區塊結構與詞彙範疇,但離硬體太遠,無法寫系統程式。
  2. CPL → BCPL(Cambridge, 1966):Martin Richards 把 CPL 簡化為 BCPL——無型別、只有「機器字(word)」一種資料單元,非常簡潔、可自舉。
  3. B(Ken Thompson, 1969):把 BCPL 移植到 PDP-7 上並進一步簡化。B 也無型別:一切資料都是一個機器字。

B 的致命問題在 PDP-11 上徹底暴露:

  • PDP-11 是位元組定址(byte-addressable)機器,而 B 只認得「字」,無法表達「存取單一 byte」
  • B 編譯出的程式太慢(直譯式的 threaded code),跑不動 Unix
  • 無型別等於無法表達浮點數、字元、結構等真實硬體有的資料形態

1969 年 Thompson 用 B 寫了第一版 Unix 雛形後,連他本人都承認:B 不足以承載 Unix 的未來。1971 年 Ritchie 開始改造 B:先加上字元型別(「NB」,New B),繼續演化,1972 年定名為 C。1973 年,兩人用 C 重寫了 Unix 核心——史上第一個由高階語言寫成、且自我承載(self-hosted)的作業系統內核。

線索與推理 -- 數學式、程式、理論#

1. 指標與記憶體模型: \(\&x\) 與 \(*p\)#

C 最重要的發明是把記憶體位址變成語言中的一級公民。C 的記憶體模型把主記憶體視為一個巨大的位元組陣列,每個變數位於某個位址:

\[ \text{Memory} = \{M[0], M[1], M[2], \dots\}, \qquad \text{type}\,:\, M[a] \to \text{byte}^k \]

兩個基本運算子構成一对互逆映射:

  • &x(取址):從變數得到它的位址, \(\&x = a\) 若 \(x \equiv M[a]\)
  • *p(解參考):從位址得到該處的變數, \(*p \equiv M[p]\)

因此對任意變數 \(x\) 與其指標 \(p\) :

\[ p = \&x \iff *p = x \iff *(\&x) = x \]

這個互逆性讓「指標」同時具備數學的優雅(它是記憶體函數的逆函數)與工程的實用(位址傳遞 = 零拷貝、直接操作硬體暫存器)。指標算術更讓陣列與指標統一:

\[ *(p + i) \equiv p[i] \equiv M[\&p[0] + i \cdot \text{sizeof}(*p)] \]

陣列索引只是一種「以元素大小為步長的指標加法」的語法糖。這個統一讓 C 能寫出極致精簡又極致高效的系統程式。

2. struct 與型別系統#

Ritchie 在 B 的無型別字上建立了完整的型別系統:char、int、float、各種長度的整數,以及最重要的 struct——把異質資料打包成自訂型別。這是資料抽象的原始形式:

struct Point { int x; int y; };   /* 自訂型別 = 場的笛卡兒積 */

數學上,struct 是型別的直積(product type):

\[ \text{Point} = \mathbb{Z} \times \mathbb{Z} \]

而陣列是冪型(power type):int a[10] 即 \(\mathbb{Z}^{10}\) 。struct 讓 C 能直接對應檔案格式與硬體暫存器配置——Unix 的 inode、現代網路協定的封包頭,全靠 struct 直接映射。

3. C 範例:linked list 與 Python 對照#

指標 + struct 的組合最經典的展示是連結串列(linked list):

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int val;
    struct Node *next;      /* 指向同型別的指標:自參考結構 */
} Node;

Node *push(Node *head, int v) {        /* 頭插法 */
    Node *n = malloc(sizeof(Node));
    n->val  = v;
    n->next = head;                     /* 指標賦值 = 接上鏈 */
    return n;
}

void print_list(Node *p) {
    for (; p != NULL; p = p->next)      /* 指標走鏈 */
        printf("%d -> ", p->val);
    printf("NULL\n");
}

int main(void) {
    Node *head = NULL;
    head = push(head, 3);               /* 3 */
    head = push(head, 2);               /* 2 -> 3 */
    head = push(head, 1);               /* 1 -> 2 -> 3 */
    print_list(head);
    return 0;
}

用 Python 對照,語義相同但記憶體模型不同:

class Node:
    def __init__(self, val, nxt=None):
        self.val, self.next = val, nxt

def push(head, v):
    return Node(v, head)          # 物件參考 ≈ 受 GC 管理的指標

head = None
for v in (3, 2, 1):
    head = push(head, v)

p = head
while p:
    print(p.val, '-> ', end='')
    p = p.next
print('NULL')

關鍵差異正是本案的核心:Python 的「指標」是由垃圾回收器管理的物件參考,程式設計師無法控制記憶體配置;C 的指標則是裸的位址,malloc/free 由程式設計師負責。前者安全但有 GC 開銷與延遲不確定性,後者高效但可能洩漏、懸吊、越界——這個取捨(control vs. safety)至今仍是 Rust(試圖兼得兩者)與所有語言設計的核心議題。

4. K&R C 到 ANSI C:標準化的旅程#

  • K&R C(1978):Kernighan 與 Ritchie 的《The C Programming Language》出版,書中語言即「K&R C」——事實標準,但有些語法含糊(如函式宣告不檢查參數型別)。
  • ANSI C / C89(1989):ANSI 成立 X3J11 委員會將 C 標準化,補上函式原型(prototype)、void 型別等;ISO 於 1990 年採納為 ISO C。從此 C 成為真正的國際標準。

5. C 與作業系統的共生#

1973 年 Unix 用 C 重寫後,開啟了「OS 內核皆 C」的傳統。原因有嚴格的工程推理:OS 內核需要 (a) 直接操作記憶體與硬體暫存器、(b) 可預測的效能(無 GC)、(c) 跨硬體移植。C 是第一門(也是此後數十年唯一)同時滿足三者的語言。於是:

  • Linux kernel(1991–):~千萬行 C
  • Windows NT kernel:C(部分 C++)
  • macOS 的 XNU kernel:C(+ 一點 C++ 與 Mach)
  • Git(2005)、SQLite、Redis、nginx:全是 C

C 與 Unix 的共生關係甚至反過來塑造了語言本身:Unix 需要 fork/exec、檔案描述子,C 就提供能自然表達它們的語法。

結案 -- 後果與影響#

  • 作業系統的母語:五十年後,幾乎所有現代 OS 內核仍以 C 為主體。你手上的 Android(Linux kernel)、iPhone 的 Darwin、雲端的每一台伺服器,核心都是 C。
  • 語言之父:C 的語法({}、if、for、函式宣告)成為整個「C 系語言家族」的模板——C++(1985)、Java(1995)、C#(2000)、JavaScript(1995)、Go(2009)、Rust(2015),語法血統皆源自 C。
  • 指標的雙刃劍:C 的記憶體模型帶來了緩衝區溢位攻擊(Morris 蠕蟲 1988 即利用之一)、懸吊指標等安全問題,催生了 ASLR、DEP 等防禦技術,並最終啟發 Rust 的所有權(ownership)模型。
  • 1978→1989 的標準化之路成為後世語言標準化(C++、SQL、POSIX)的範本。

關鍵人物與文獻#

  • Dennis Ritchie(1941–2011):C 語言之父、Unix 共同創建者,1983 年圖靈獎(與 Thompson 同獲)。C 的型別系統、指標語義、struct 皆出自他的設計。
  • Ken Thompson:B 語言作者——B 的失敗正是 C 誕生的前因;Unix 的第一批功能由他用組合語言與 B 寫成。
  • Martin Richards:BCPL(1966)作者,B 與 C 的共同祖先;BCPL 的 !p(解參考)與 lv(取址)是指標語法的直系祖先。
  • Brian Kernighan:K&R C 的「K」,《The C Programming Language》第一作者,也是第一個寫出 "hello, world" 範例的人。
  • 文獻:Kernighan & Ritchie, The C Programming Language(1978);Ritchie, "The Development of the C Language"(1993);ISO/IEC 9899(C89 及後續標準)。