GCC

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（`GNU Pascal`）、Modula-2、Modula-3、Mercury語言以及VHDL（`GHDL`）。一些實驗性分支可支持更多語言，比如GCC UPC編譯器還支持UPC和Rust。

## 支援的處理器架構

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

- AArch64
- Alpha
- ARM
- AVR
- Blackfin
- eBPF
- Adapteva（GCC 4.8）
- Hitachi H8
- HC12
- IA-32 (x86)
- IA-64 （Intel安騰）
- MIPS
- Motorola 68000
- MSP430
- Nvidia GPU
- Nvidia並行指令集
- PA-RISC
- PDP-11
- PowerPC
- R8C / M16C / M32C
- RISC-V
- SPARC
- SuperH
- System/390 / zSeries
- VAX
- x86-64

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

- 68HC11
- A29K
- C6x
- CR16
- D30V
- DSP16xx
- ETRAX CRIS
- Fujitsu FR
- FR-V
- IBM ROMP
- Intel i960
- IP2000
- M32R
- MCORE
- MIL-STD-1750A
- MMIX
- MN10200
- MN10300
- Motorola 88000
- NS320xx
- RL78
- Stormy16
- V850
- Xtensa

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

- Cortus APS3
- ARC
- AVR32
- C166和C167
- D10V
- EISC
- eSi-RISC
- Hexagon
- LatticeMico32
- LatticeMico8
- MeP
- MicroBlaze
- Motorola 6809
- MRISC32
- MSP430
- NEC SX architecture
- Nios II和Nios嵌入式處理器
- OpenRISC
- PDP-10
- PIC30
- PIC30
- Parallax Propeller
- Saturn (HP48XGCC)
- System/370
- TIGCC (m68k變種)
- TMS9900
- TriCore
- Z8000
- ZPU

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 項目在GPLv3的許可下實現了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.

## 外部連結

- 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專案。

## 基本資料

- 日語原文：GNU編譯器套裝
- 開發者：GNU計劃
- 發行日期：1987年5月23日
- 軟體版本週期：13.4
- 程式語言：C++
- 作業系統：跨平台
- 語言：英語
- 類型：編譯器
- 許可協議：GNU通用公共許可證第三版或更新