跳轉到

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;
}

相關條目#