06.2 GCC與Binutils:編譯器工具鏈#
從 Pastwatch 到 GCC#
1980 年代中期的 Stallman 面對現實:GNU 要做出自由作業系統,最先進來的專案竟是個編譯器。1986 年他先寫了 GNU C Compiler 的原型(部分基於 Lawrence Livermore 實驗室一個由 Pastwatch 計畫演化的編譯器,Pastel 語言的前端),1987 年正式發布 GCC 1.0。選擇先做 C 編譯器是戰略性的:沒有編譯器,GNU 就無法自舉(用自由工具打造自由工具);有了編譯器,整個 GNU 工具鏈與日後的 Linux 才有可能。
RMS 的設計理念有兩條主軸。其一是可攜性:編譯器必須能輕易移植到任何 CPU,這在當時是急務——自由軟體不能依賴任何特定硬體。其二是架構分層:GCC 以 RTL(Register Transfer Language)作為核心中間表示,前端把 C 轉成 RTL,各 CPU 的後端把 RTL 化為機器碼。移植新平台只需寫機器描述(machine description),這讓 GCC 在數年內橫跨了數十種處理器,從 VAX 到 SPARC 到嵌入式晶片。
編譯管線與優化的演化#
graph LR
SRC["C 原始碼"] --> CPP["預處理器 cpp<br/>巨集展開/標頭檔"]
CPP --> FE["前端<br/>剖析/語意分析"]
FE --> IR["中間表示<br/>RTL → GIMPLE/SSA (2004)"]
IR --> OPT["優化器<br/>常數摺疊/死碼消除/迴圈優化<br/>暫存器分配/指令排程"]
OPT --> BE["後端<br/>機器描述 → 組合語言"]
BE --> AS["as 組譯器"]
AS --> OBJ["目標檔 .o"]
OBJ --> LD["ld 連結器"]
LD --> EXE["可執行檔"]GCC 的優化技術構成一部壓縮的編譯理論史:常數摺疊與傳播把 \(\text{int}\ x = 2+3\) 直接變成 5;死碼消除刪掉永遠執行不到的程式;暫存器分配以圖著色理論把無限多的虛擬暫存器塞進有限的實體暫存器;指令排程重排指令以填補管線空泡。2004 年,GCC 4.0 引入 GIMPLE 表示與 SSA(靜態單賦值形式,每個變數只被賦值一次,使資料流分析大為簡化),這次內部重構由 Red Hat 主導,標誌著 GCC 從個人專案轉為有組織的社群工程。使用者透過 -O0 到 -O3(還有 -Os 針對大小)選擇優化等級,這套旗標成了產業的通用語言。
Binutils:編譯器的兄弟們#
編譯器不是孤島。GCC 產出的組合語言要靠 as 組譯成目標碼;多個目標檔與函式庫要靠 ld 連結成可執行檔;檢視產物用 objdump、封裝靜態函式庫用 ar——這組工具合稱 Binutils,由 FSF 啟動、後來主要由 Red Hat 的工程師維護。連結器解決的是另一類深刻問題:符號解析(這個 printf 到底指著誰)與重定位(把相對位址補成最終位址)。
ELF:動態連結的勝利#
1990 年代前,Unix 世界有兩種主流目標檔格式:古老的 a.out 與 AT&T 的 COFF。它們難以擴充、不利於動態連結。由 AT&T USL 與 Sun 共同設計的 ELF(Executable and Linking Format)應運而生:Sun 在 SunOS 4.0(1988)率先採用,隨後 System V Release 4(1989)將其定為標準,Linux 在 1995 年前後也完成了從 a.out 到 ELF 的轉移。ELF 支援共享函式庫的動態連結——程式執行時才把 libc.so 映射進位址空間,多個程式共享一份程式碼頁,既省記憶體(這在磁芯記憶體時代是命題,在 RAM 普及後依然攸關快取效率),又讓修安全漏洞不必重編譯所有程式。今天你手機裡的每一個執行檔都是 ELF。
制度意義:自由的基石與挑戰者#
GCC 的最大歷史功績是制度性的:它證明一個自由、可攜、品質達到商業水準的編譯器可以存在,並且成為整個生態的地基。沒有 GCC,Linux 無法編譯自己(自舉);沒有 GCC,BSD、後來的各種架構移植都要慢上許多。它也是「自舉哲學」的典範:GCC 3.x 之後的版本,就是用 GCC 自己編譯出來的。
但獨大也帶來了代價。GCC 的程式碼庫龐雜、內部耦合重,且 RMS 曾以授權限制其使用於其他專案。2000 年代,蘋果因授權與工程考量轉向 LLVM——Chris Lattner 在伊利諾大學的博士專案,2003 年成為開源專案,2005 年蘋果雇用 Lattner,將 LLVM 用於 Xcode、OpenGL 與日後的 Swift。LLVM/Clang 以模組化函式庫的架構(前端、中端、後端皆可獨立呼叫)對決 GCC 的單體式傳統,加上寬鬆的授權,逐漸贏得 FreeBSD、Chrome、Android(NDK)與眾多新語言(Rust、Swift、Julia)的青睞。2010 年代 GCC 也在壓力下重構、改採寬鬆的授權例外(runtime library 的 GCC Runtime Library Exception)。
編譯器工具鏈的故事告訴我們:一個領域的「基礎建設」一旦形成,就會自我強化數十年;而挑戰者要贏,靠的不是單點技術,而是更好的架構邊界與更友善的制度設計。GCC 與 LLVM 的雙雄並立,正是這條規律最好的註腳。