GNU编译器套装

**GNU 編譯器套裝**（英語：**GNU Compiler Collection**，縮寫為 **GCC**）是 GNU 計劃製作的一種最佳化編譯器，支援各種程式語言、作業系統、電腦系統結構。該編譯器是以 GPL 及 LGPL 授權條款所發行的自由軟體，也是 GNU 計劃的關鍵部分，還是 GNU 工具鏈的主要組成部份之一。GCC（特別是其中的 C 語言編譯器）也常被認為是跨平台編譯器的事實標準。1985 年由理察・馬修・斯托曼開始發展，現在由自由軟體基金會負責維護工作。截至 2019 年，GCC 大約有 1500 萬行代碼，是現存最大的自由程式之一。 它在自由軟體的發展中發揮了重要作用，不僅是一個工具，還是一個典例。

原名為 **GNU C 語言編譯器**（**GNU C Compiler**），因為它原本只能處理 C 語言。同年 12 月，新的 GCC 編譯器可以編譯 C++ 語言。後來又為 Fortran、Pascal、Objective-C、Java、Ada，Go 等其他語言開發了前端。C 和 C++ 編譯器也支援 OpenMP 和 OpenACC 規範。

GCC 編譯器已經被移植到比其他編譯器更多的平台和指令集架構上，並被廣泛部署在開發自由和專有軟體的工具中。GCC 還可用於許多嵌入式系統，包括基於 ARM 和 Power ISA 的晶片。

GCC 不僅是 GNU 作業系統的官方編譯器，還是許多類 UNIX 系統和 Linux 發行版的標準編譯器。BSD 家族中的大部分作業系統也在 GCC 釋出之後轉用 GCC；不過 FreeBSD、OpenBSD 和 Apple macOS 已經轉向了 Clang 編譯器，主要是因為授權問題。GCC 也可以編譯 Windows、Android、iOS、Solaris、HP-UX、IBM AIX 和 DOS 系統的代碼。GCC 原本用 C 開發，後來因為 LLVM、Clang 的崛起，它更快地將開發語言轉換為 C++。許多 C 的愛好者在對 C++ 一知半解的情況下主觀認定 C++ 的效能一定會輸給 C，但是 Ian Lance Taylor 給出了不同的意見，並表明 C++ 不但效能不輸給 C，而且能設計出更好，更容易維護的程式。

## 歷史

1983 年底，為了引導 GNU 作業系統，理察・馬修・斯托曼向阿姆斯特丹編譯器套件（自由大學編譯器套件）的作者安德魯・塔能鮑姆請求在 GNU 上允許使用該編譯器；但是作者告知他該編譯器僅對大學免費。因此，他打算開發一個不同的編譯器。一開始他打算與 Len Tower 和其他人將勞倫斯利佛摩國家實驗室的一個現有編譯器從 Pastel 覆寫成 C。但是他在給利弗莫爾編譯器寫了一個新的 C 前端後，發現它需要數百萬位元組的堆疊空間，只有 64KB 的 68000 Unix 系統上無法執行。因此，他打算自己從頭寫一個編譯器。總而言之，儘管斯托曼確實使用了他自己寫的 C 前端，他並沒有將任何 Pastel 編譯器的代碼放在 GCC 中。

GCC 於 1987 年 3 月 22 日在麻省理工學院的檔案傳輸協定上釋出，斯托曼被列為作者，也提及了其他人並感謝他們的貢獻：Jack Davidson 和 Christopher Fraser 給出了使用暫存器傳遞語言作為中間語言的思路；Paul Rubin 為預處理器貢獻良多；以及 Leonard Tower 寫了「部分解析器、RTL 生成器、RTL 定義和 Vax 機器描述」。被 Peter H. Salus 譽為「自由軟體第一擊」的 GNU 編譯器釋出正值昇陽電腦將其作業系統與其開發工具解綁，並提價單獨出售。這使得許多客戶購買或下載 GCC 而非供應商的工具。儘管斯托曼認為 GNU Emacs 是他的主要工程，但截至 1990 年，GCC 支援 13 種電腦架構，效能比其他編譯器優越並為商業所用。

### EGCS 克隆

由於 GCC 是在 GPL 授權下授權的，其他為 C 以外語言編寫介面的程式設計師可以自由的開發其自己的編譯器分支，只要他們遵守 GPL 授權條款。但是，多分叉在日後體現出低效和不便的特點；而且人們很難使熱愛穩定性勝過新特性的 GCC 官方專案接受他們的分支。FSF 對添加到 GCC 2.x 官方版本（1992 年開始開發）中的內容進行了相當嚴格的控制，以至於被 Eric S. Raymond 在《大教堂與集市》中形容為 "大教堂" 開發模式。

在 1997 年，一群不滿 GCC 緩慢且封閉的創作環境者，組織了一個名為實驗性 / 增強型 GNU 編譯器系統（Experimental/Enhanced GNU Compiler System）的專案，將幾個實驗性分叉合併為一個專案。其基礎是 GCC 的開發快照（大概取自 2.7.2，後來跟進到 2.8.1）。合併內容包括 g77（Fortran）、PGCC（P5 Pentium 最佳化的 GCC），許多 C++ 的改進，以及許多新的架構和作業系統變種。

這兩個專案都密切觀察著彼此的動態，但是 EGCS 的發展明顯更活躍，因此 FSF 正式停止他們對 GCC 2.x 編譯器的開發並希望 EGCS 成為 GCC 的官方版本。在 1999 年 4 月 EGCS 專案被任命為為 GCC 的維護者。隨著 1999 年 7 月 GCC 2.95 的釋出，這兩個專案再次聯合起來。此後，GCC 在一個指導委員會的指導下，來自各國的程式設計師小組會對其進行維護。

由於缺乏維護，GCC 3 (2002) 移除了 CHILL 的前端支援。在版本 4.0 之前，GCC 3 中的 Fortran 前端是 `g77`，只支援 FORTRAN 77。該前端後來被廢棄，取而代之的是新 GNU Fortran 前端，支援 Fortran 95 和 Fortran 2003 及 Fortran 2008 的大部分內容 。從 GCC 4.8 版開始，GCC 由 C++ 語言編寫。從 GCC 5 到 GCC 7 都保留了對 Cilk Plus 的支援。

GCC 已經被移植到各種指令集架構上，並被廣泛部署為開發自由或專有軟體的工具。GCC 還可用於許多嵌入式系統，包括 Symbian（稱為 gcce）、基於 ARM 和基於 Power ISA 的晶片。該編譯器可以在各種平台上輸出，包括遊戲控制器中的 PS2、Cell 微處理器架構的 PS3 以及 Dreamcast。相比於其他編譯器，GCC 編譯器被部署在更多的作業系統和處理器上。

## 目前支援的語言

截至 2022 年 9 月，GCC 12.2 版內含 C（`gcc`）、C++（`g++`）、Objective-C、Fortran（`gfortran`）、Ada（GNAT）、Go （`gccgo`）以及 D (`gdc`，從 9.1 版開始) 程式語言的前端。OpenMP 和 OpenACC 並列語言拓展從 GCC 5.1 開始支援。GCC 7 之前的版本也支援 Java（`gcj`），允許將 java 編譯為機器語言。

有關 C++ 和 C 的語言版本支援，從 GCC 11.1 開始預設為 _gnu++17_，C++17 的超集；以及 _gnu11_，C11 的超集，還提供嚴格的標準支援。GCC 也對 C++20 和即將到來的 C++23 標準提供實驗性部分支援。

有許多為其它語言編寫的第三方前端，比如 Pascal（`gpc`）、Modula-2、Modula-3、Mercury 語言以及 VHDL（`GHDL`）。一些實驗性分支可支援更多語言，比如 GCC UPC 編譯器還支援 UPC 和 Rust。

## 支援的處理器架構

GCC 11.1 版本支援的處理器包括：

標準版本支援的少見處理器如下：

非 FSF 維護的 GCC 版本支援的處理器如下：

GCJ Java 編譯器可以輸出機器語言或者 Java 虛擬機器的 Java 位元組碼。當重新導向 GCC 到新的平台上，經常會用到自舉。 Motorola 68000，Zilog Z80 以及其他處理器也可在為德州儀器、惠普、夏普以及卡西歐可程式化繪圖計算機設計的 GCC 編譯器上輸出。

## 設計

GCC 的外部介面遵循 UNIX 使用慣例。使用者輸入特定語言的驅動程式碼（C 語言為 `gcc`，C++ 為 `g++`，如此不一而足），該程式解釋命令語句，呼叫實際編譯器，在輸出介面上執行組譯器，然後選擇性地執行連結器，產生一個完整的可執行二進位檔案。

每種語言的編譯器都是一個獨立的程式，可讀取原始碼並輸出機器碼。所有語言的編譯器都擁有共通的中介架構：各語言前端解析符合此語言的原始碼，並產生一抽象語法樹。如有必要，這些代碼會被轉換為中介端的輸入表示，即所謂的 _GENERIC_ 形式；然後中介端會逐漸將程式轉換為最終形式。編譯器最佳化和靜態代碼分析技術（例如 FORTIFY\_SOURCE，一種嘗試發現緩衝區溢位的編譯器指令）也會在原始碼編譯時應用。這些操作都是在多種表示法上工作，其中主要是獨立於架構的 GIMPLE 表示法和獨立於架構的 RTL 表示法。最終，機器碼由傑克・戴維森與克里斯・弗雷澤發明的演算法產生。

除了 Ada 前端主要以 Ada 寫成，GCC 大部分是用 C 語言編寫的。GCC 發行版包含主要以各自語言編寫的 Ada 和 C++ 標準庫。在一些平台上，GCC 發行版還包括一個低階執行庫 `libgcc` 該執行庫由獨立於機器的 C 語言和特定處理器的機器碼組合編寫，可處理目標處理器不能直接執行的複雜算術運算。

GCC 使用了許多額外的工具。雖然這些工具在 UNIX 和 Linux 發行版中基本為預設安裝的，但是 Windows 系統通常沒有。這些工具包括 Perl、Flex、Bison 和其他常用工具；還需要額外的依賴庫 GMP、MPC 和 MPFR。

2010 年 5 月，GCC 指導委員會決定允許使用 C++ 編譯器來編譯 GCC。 編譯器計劃主要用 C 語言編寫，並加上 C++ 的一個子集特性。之所以這樣做是為了了讓 GCC 的開發者能夠使用 C++ 的解構器和泛型功能。2012 年 8 月，GCC 指導委員會宣布，GCC 將以 C++ 為源語言。這意味著，要從原始碼編寫 GCC 編譯器，需要一個能夠理解 ISO/IEC C++03 標準的 C++ 編譯器。2020 年 5 月 18 日，GCC 從 ISO/IEC C++03 標準轉向 ISO/IEC C++11 標準（即需要覆寫編譯器本身；預設情況下可編譯 C++ 早期版本）。

### 前端介面

每個前端都使用一個剖析器來產生給定的原始碼的一個抽象語法樹。由於語法樹的抽象性，不同語言的原始碼都可以被同一個後端處理。GCC 一開始使用 bison 生成的 LALR 語法剖析器，但在 2004 年逐漸轉向用於 C++ 的遞迴下降解析器，並在 2006 年用於 C 和 Objective-C。2021 年開始，所有前端都使用遞迴下降解析器。

在 GCC 4.0 之前，程式的語法樹結構不完全獨立於輸出的目標處理器架構。對於不同語言的前端來說，語法樹的含義可能不同；而且前端可以提供它們特別的語法樹規則。隨著 GENERIC 和 GIMPLE 的引入，這種情況得以避免。這是兩種新的獨立於語言的語法樹形式，隨 GCC 4.0 引入編譯器前端。GENERIC 更複雜，是一種基於 GCC 3.x Java 前端的中介表示。 GIMPLE 是一個簡化的 GENERIC，其中各種結構被簡化為多個 GIMPLE 指令。 C、C++ 和 Java 前端直接在前端生成 GENERIC。 相反，其他前端在解析後會有不同的中介表示，這些中介表示將轉換為 GENERIC。前端生成 GENERIC 之後再使用「gimplifier」技術簡化 GENERIC 的複雜結構，成為一較簡單的以 SSA 為基礎的 GIMPLE 形式，一種強大的，獨立於語言和體系結構的全域（函式範圍）最佳化的通用語言。

### 中介介面

#### GENERIC 和 GIMPLE

GENERIC 是一種中間表示語言，在將原始碼編譯成可執行二進位檔案時用作「中介端」。GCC 的所有前端都指向 GENERIC 的子集 GIMPLE。GCC 的中間階段進行所有的獨立於編譯語言和目標架構的代碼分析和最佳化，從 GENERIC 表示法開始將其轉譯為暫存器傳遞語言（RTL）。GENERIC 表示只包含中介端最佳化後的指令式編程結構的子集。

在將原始碼轉譯為 GIMPLE 表示時，會使用臨時變數將複雜表達式拆分為三位址碼。這種表示法的靈感來自於 Laurie J. Hendren 在 McCAT 編譯器中提出的 SIMPLE 表示法，用於簡化指令式程式的分析和最佳化。

#### 最佳化

一般編譯器作者會將語法樹的最佳化放在前端，但其實此步驟並不看語言的種類而有不同，且不需要用到語法解析器。因此 GCC 作者們將此步驟歸入通稱為**中介階段**的部分裡。此類的最佳化包括消解死碼、消解重複運算與全域數值重編碼等。許多最佳化技巧也正在實作中。

### 後端介面

GCC 的後端部分是由預處理器宏和目標架構特有的函式指定的，例如定義其位元組序、字大小和呼叫約定。後端的前半部分使用這些來決定 RTL 的生成形式，因此雖然 GCC 的 RTL 理論上不受處理器影響，但在此階段其抽象指令已被轉換成目標架構的格式。在任何時候，構成程式的實際 RTL 指令都必須符合目標架構的機器描述標準。

機器描述檔案包含了 RTL 模式、運算元約束和輸出最終組譯的代碼片段。這些約束條件表明，一個特定的 RTL 模式可能只適用於某些硬體暫存器，或者某些只允許有限大小的即時運算元偏移（例如 12、16、24、... 位偏移，等等）的架構。在 RTL 生成過程中，給定目標架構的約束條件會被檢查。為了釋出一個給定的 RTL 片段，它必須與機器描述檔案中的一個或多個 RTL 模式相匹配，並滿足該模式的約束條件；否則，就無法將最終的 RTL 轉換成機器碼。

在編譯結束時，有效 RTL 會被簡化為嚴格的形式，其中每條指令都指向真實的機器暫存器、和目標機器描述檔案中的一種模式。嚴格化 RTL 是個相當複雜的工作：首先是暫存器分配，選擇真實的硬體暫存器來取代最初分配的偽暫存器；還有多載，未分配實際硬體暫存器的偽暫存器都會被溢位到堆疊中，並生成執行此溢位的 RTL。過大的偏移量無法適合實際指令，故會被分解成服從偏移量約束的 RTL 序列。

後端在最後通過呼叫與每個模式相關聯的一小段代碼來構建機器碼，以使用在多載時選擇的最終暫存器、偏移量和位址從目標指令集中生成真正的指令。當組譯生成片段只是一個字串時，就會執行暫存器、偏移量和 / 或位址到字串的簡單字串替換。組譯生成片段也可以是一個簡短的 C 代碼塊，但最終也會返回一個包含有效組譯代碼的字串。

### C++ 標準庫

GCC 專案在 CPLv3 的授權下實現了 C++ 標準庫（libstdc++）。目前的最新版本是 11。

## 替 GCC 程式除錯

GNU 除錯器是一個為 GCC 除錯的程式。其他特殊用途的除錯工具是 Valgrind，用以發現記憶體流失（memory leak）。而 GNU 測量器（gprof）可以得知程式中某些函式花費多少時間，以及其呼叫頻率；此功能需要使用者在編譯時選定**測量**（profiling）選項。

## GCC 內嵌組譯

內嵌組譯也稱行內組譯，是把組合語言代碼塊插在 C 語言語句之間。詳情參見 GCC-Inline-Assembly-HOWTO.html（頁面存檔備份，存於網際網路檔案館）

## 參考文獻

### 參照

### 來源

* Richard M. Stallman：_Using and Porting the GNU Compiler Collection（頁面存檔備份，存於網際網路檔案館）_ , Free Software Foundation，ISBN 0-595-10035-X
* Richard M. Stallman: _Using Gcc: The Gnu Compiler Collection Reference（頁面存檔備份，存於網際網路檔案館）_ , Free Software Foundation, ISBN 1-882114-39-6
* Brian J. Gough：_An Introduction to GCC_, Network Theory Ltd., ISBN 0-9541617-9-3

## 更多閱讀

* Arthur Griffith, _GCC: The Complete Reference_. McGrawHill/Osborne. ISBN 0-07-222405-3.
* Kerner, Sean Michael. Open Source GCC 4.0: Older, Faster. internetnews.com. April 22, 2005 [2006-12-27]. （原始內容存檔於 2006-09-17）.
* Kerner, Sean Michael. New GCC Heavy on Optimization. internetnews.com. March 2, 2006 [2006-12-27]. （原始內容存檔於 2006-10-22）.

## 外部連結

* GCC 官方網站（頁面存檔備份，存於網際網路檔案館）
* YouTube 上的淺談 GCC 編譯技術 Break Compilation Boundaries with GCC
* GCC Forum- 由 Nabble（頁面存檔備份，存於網際網路檔案館）維持，整理所有 gcc 通訊討論串，並整合入一個可搜尋介面中。

## 參見

* GCC 目前包含了貝姆垃圾收集器，一個為 C/C++ 所設計的垃圾回收器。
* distcc - 為分散式編譯所設計的軟體，以 GCC 為協同軟體。
* ccache - 用於快取編譯的中間結果，加快重新編譯的速度。
* LLVM - 低層虛擬機器編譯器架構，其中的 clang (Obj-) C (++) 編譯器實現了大部分 GNU C 拓展。
* MinGW - 將 GNU 開發工具移植到 Win32 平臺下的計畫
* Cygwin - 在 Windows 上執行 Unix 程式的類比軟體。
* GCC Summit
* OpenWatcom - 另一個開放原碼的 C++/Fortran 編譯器。
* Code Sourcery - 一個 GCC 顧問公司。
* ggcc - 全球化 GCC 專案。