Loading AI tools
支援各種程式語言的編譯器系統 来自维基百科,自由的百科全书
GNU編譯器套裝(英語:GNU Compiler Collection,縮寫為GCC)是GNU計劃製作的一種最佳化編譯器,支援各種程式語言、作業系統、電腦系統結構。該編譯器是以GPL及LGPL授權條款所發行的自由軟體,也是GNU計劃的關鍵部分,還是GNU工具鏈的主要組成部份之一。GCC(特別是其中的C語言編譯器)也常被認為是跨平台編譯器的事實標準。1985年由理察·馬修·斯托曼開始發展,現在由自由軟體基金會負責維護工作。截至2019年,GCC大約有1500萬行代碼,是現存最大的自由程式之一。[2] 它在自由軟體的發展中發揮了重要作用,不僅是一個工具,還是一個典例。
原名為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編譯器[3],主要是因為授權問題。[4][5][6]GCC也可以編譯Windows、Android、iOS、Solaris、HP-UX、IBM AIX和DOS系統的代碼。GCC原本用C開發,後來因為LLVM、Clang的崛起,它更快地將開發語言轉換為C++。許多C的愛好者在對C++一知半解的情況下主觀認定C++的效能一定會輸給C,但是Ian Lance Taylor給出了不同的意見,並表明C++不但效能不輸給C,而且能設計出更好,更容易維護的程式[7][8]。
1983年底,為了引導GNU作業系統,理察·馬修·斯托曼向阿姆斯特丹編譯器套件(自由大學編譯器套件)的作者安德魯·塔能鮑姆請求在GNU上允許使用該編譯器;但是作者告知他該編譯器僅對大學免費。因此,他打算開發一個不同的編譯器。[9]一開始他打算與Len Tower和其他人將勞倫斯利佛摩國家實驗室的一個現有編譯器從Pastel覆寫成C。[10][11]但是他在給利弗莫爾編譯器寫了一個新的C前端後,發現它需要數百萬位元組的堆疊空間,只有64KB的68000 Unix系統上無法執行。因此,他打算自己從頭寫一個編譯器。[10]總而言之,儘管斯托曼確實使用了他自己寫的C前端,他並沒有將任何Pastel編譯器的代碼放在GCC中。[10][12]
GCC於1987年3月22日在麻省理工學院的檔案傳輸協定上釋出[13],斯托曼被列為作者,也提及了其他人並感謝他們的貢獻:Jack Davidson和Christopher Fraser給出了使用暫存器傳遞語言作為中間語言的思路;Paul Rubin為預處理器貢獻良多;以及Leonard Tower寫了「部分解析器、RTL生成器、RTL定義和Vax機器描述」。[14]被Peter H. Salus譽為「自由軟體第一擊」的GNU編譯器釋出正值昇陽電腦將其作業系統與其開發工具解綁,並提價單獨出售。這使得許多客戶購買或下載GCC而非供應商的工具。[15]儘管斯托曼認為GNU Emacs是他的主要工程,但截至1990年,GCC支援13種電腦架構,效能比其他編譯器優越並為商業所用。[16]
由於GCC是在GPL授權下授權的,其他為C以外語言編寫介面的程式設計師可以自由的開發其自己的編譯器分支,只要他們遵守GPL授權條款。但是,多分叉在日後體現出低效和不便的特點;而且人們很難使熱愛穩定性勝過新特性的GCC官方專案接受他們的分支。[17]FSF對添加到GCC 2.x官方版本(1992年開始開發)中的內容進行了相當嚴格的控制,以至於被Eric S. Raymond在《大教堂與集市》中形容為 "大教堂 "開發模式。
在1997年,一群不滿GCC緩慢且封閉的創作環境者,組織了一個名為實驗性/增強型GNU編譯器系統(Experimental/Enhanced GNU Compiler System)的專案[17][12],將幾個實驗性分叉合併為一個專案。其基礎是GCC的開發快照(大概取自2.7.2,後來跟進到2.8.1)。合併內容包括g77(Fortran)、PGCC(P5 Pentium最佳化的GCC)[12],許多C++的改進,以及許多新的架構和作業系統變種。[18]
這兩個專案都密切觀察著彼此的動態,但是EGCS的發展明顯更活躍,因此FSF正式停止他們對GCC 2.x編譯器的開發並希望EGCS成為GCC的官方版本。在1999年4月EGCS專案被任命為為GCC的維護者。隨著1999年7月GCC 2.95的釋出,這兩個專案再次聯合起來。[19][12]此後,GCC在一個指導委員會的指導下,來自各國的程式設計師小組會對其進行維護。[20]
由於缺乏維護,GCC 3 (2002)移除了CHILL的前端支援。[21]在版本4.0之前,GCC 3中的Fortran前端是g77
,只支援FORTRAN 77。該前端後來被廢棄,取而代之的是新GNU Fortran前端,支援Fortran 95和Fortran 2003及Fortran 2008的大部分內容 。[22][23]從GCC 4.8版開始,GCC由C++語言編寫。[24]從GCC 5到GCC 7都保留了對Cilk Plus的支援。[25][26]
GCC已經被移植到各種指令集架構上,並被廣泛部署為開發自由或專有軟體的工具。GCC還可用於許多嵌入式系統,包括Symbian(稱為gcce)[27]、基於ARM和基於Power ISA的晶片。[28]該編譯器可以在各種平台上輸出,包括遊戲控制器中的PS2[29]、Cell微處理器架構的PS3[30]以及Dreamcast。[31]相比於其他編譯器,GCC編譯器被部署在更多的作業系統和處理器上。[32]
截至2022年9月,GCC 12.2版內含C(gcc
)、C++(g++
)、Objective-C、Fortran(gfortran
)、Ada(GNAT)、Go (gccgo
)以及D (gdc
,從9.1版開始)[33]程式語言的前端。[34]OpenMP和OpenACC並列語言拓展從GCC 5.1開始支援。[35][36]GCC 7之前的版本也支援Java(gcj
),允許將java編譯為機器語言。[37]
有關C++和C的語言版本支援,從GCC 11.1開始預設為gnu++17,C++17的超集;以及gnu11,C11的超集,還提供嚴格的標準支援。GCC也對C++20和即將到來的C++23標準提供實驗性部分支援。[38]
有許多為其它語言編寫的第三方前端,比如Pascal(gpc
)、Modula-2、Modula-3、Mercury語言以及VHDL(GHDL
)。[34]一些實驗性分支可支援更多語言,比如GCC UPC編譯器還支援UPC[39]和Rust。[40]
GCC 11.1版本支援的處理器包括:[41]
標準版本支援的少見處理器如下:
非FSF維護的GCC版本支援的處理器如下:
GCJ Java編譯器可以輸出機器語言或者Java虛擬機器的Java位元組碼。[44]當重新導向GCC到新的平台上,經常會用到自舉。 Motorola 68000,Zilog Z80以及其他處理器也可在為德州儀器、惠普、夏普以及卡西歐可程式化繪圖計算機設計的GCC編譯器上輸出。[45]
GCC的外部介面遵循UNIX使用慣例。使用者輸入特定語言的驅動程式碼(C語言為gcc
,C++為g++
,如此不一而足),該程式解釋命令語句,呼叫實際編譯器,在輸出介面上執行組譯器,然後選擇性地執行連結器,產生一個完整的可執行二進位檔案。
每種語言的編譯器都是一個獨立的程式,可讀取原始碼並輸出機器碼。所有語言的編譯器都擁有共通的中介架構:各語言前端解析符合此語言的原始碼,並產生一抽象語法樹。如有必要,這些代碼會被轉換為中介端的輸入表示,即所謂的 GENERIC 形式;然後中介端會逐漸將程式轉換為最終形式。編譯器最佳化和靜態代碼分析技術(例如FORTIFY_SOURCE[46],一種嘗試發現緩衝區溢位的編譯器指令)也會在原始碼編譯時應用。這些操作都是在多種表示法上工作,其中主要是獨立於架構的GIMPLE表示法和獨立於架構的RTL表示法。最終,機器碼由傑克·戴維森與克里斯·弗雷澤發明的演算法產生。
除了Ada前端主要以Ada寫成,GCC大部分是用C語言編寫的。GCC發行版包含主要以各自語言編寫的Ada和C++標準庫。[47]在一些平台上,GCC發行版還包括一個低階執行庫libgcc
該執行庫由獨立於機器的C語言和特定處理器的機器碼組合編寫,可處理目標處理器不能直接執行的複雜算術運算。[48]
GCC使用了許多額外的工具。雖然這些工具在UNIX和Linux發行版中基本為預設安裝的,但是Windows系統通常沒有。這些工具包括Perl、Flex、Bison和其他常用工具;還需要額外的依賴庫GMP、MPC和MPFR。[49]
2010年5月,GCC指導委員會決定允許使用C++編譯器來編譯GCC。[50] 編譯器計劃主要用C語言編寫,並加上C++的一個子集特性。之所以這樣做是為了了讓GCC的開發者能夠使用C++的解構器和泛型功能。[51]2012年8月,GCC指導委員會宣布,GCC將以C++為源語言。[52]這意味著,要從原始碼編寫GCC編譯器,需要一個能夠理解ISO/IEC C++03標準的C++編譯器。2020年5月18日,GCC從ISO/IEC C++03標準轉向ISO/IEC C++11標準(即需要覆寫編譯器本身;預設情況下可編譯C++早期版本)。[53]
每個前端都使用一個剖析器來產生給定的原始碼的一個抽象語法樹。由於語法樹的抽象性,不同語言的原始碼都可以被同一個後端處理。GCC一開始使用bison生成的LALR語法剖析器,但在2004年逐漸轉向用於C++的遞迴下降解析器[54],並在 2006 年用於C和Objective-C[55]。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 是一種中間表示語言,在將原始碼編譯成可執行二進位檔案時用作「中介端」。GCC的所有前端都指向GENERIC的子集GIMPLE。GCC 的中間階段進行所有的獨立於編譯語言和目標架構的代碼分析和最佳化,從 GENERIC 表示法開始[56]將其轉譯為暫存器傳遞語言(RTL)。GENERIC 表示只包含中介端最佳化後的指令式編程結構的子集。
在將原始碼轉譯為GIMPLE表示時[57],會使用臨時變數將複雜表達式拆分為三位址碼。這種表示法的靈感來自於 Laurie J. Hendren[58] 在 McCAT 編譯器[59]中提出的 SIMPLE 表示法,用於簡化指令式程式的分析和最佳化。
一般編譯器作者會將語法樹的最佳化放在前端,但其實此步驟並不看語言的種類而有不同,且不需要用到語法解析器。因此GCC作者們將此步驟歸入通稱為中介階段的部分裡。此類的最佳化包括消解死碼、消解重複運算與全域數值重編碼等。許多最佳化技巧也正在實作中。
GCC的後端部分是由預處理器宏和目標架構特有的函式指定的,例如定義其位元組序、字大小和呼叫約定。後端的前半部分使用這些來決定RTL的生成形式,因此雖然GCC的RTL理論上不受處理器影響,但在此階段其抽象指令已被轉換成目標架構的格式。在任何時候,構成程式的實際RTL指令都必須符合目標架構的機器描述標準。
機器描述檔案包含了RTL模式、運算元約束和輸出最終組譯的代碼片段。這些約束條件表明,一個特定的RTL模式可能只適用於某些硬體暫存器,或者某些只允許有限大小的即時運算元偏移(例如12、16、24、...位偏移,等等)的架構。在RTL生成過程中,給定目標架構的約束條件會被檢查。為了釋出一個給定的RTL片段,它必須與機器描述檔案中的一個或多個RTL模式相匹配,並滿足該模式的約束條件;否則,就無法將最終的RTL轉換成機器碼。
在編譯結束時,有效RTL會被簡化為嚴格的形式,其中每條指令都指向真實的機器暫存器、和目標機器描述檔案中的一種模式。嚴格化RTL是個相當複雜的工作:首先是暫存器分配,選擇真實的硬體暫存器來取代最初分配的偽暫存器;還有多載,未分配實際硬體暫存器的偽暫存器都會被溢位到堆疊中,並生成執行此溢位的 RTL。過大的偏移量無法適合實際指令,故會被分解成服從偏移量約束的RTL序列。
後端在最後通過呼叫與每個模式相關聯的一小段代碼來構建機器碼,以使用在多載時選擇的最終暫存器、偏移量和位址從目標指令集中生成真正的指令。當組譯生成片段只是一個字串時,就會執行暫存器、偏移量和/或位址到字串的簡單字串替換。組譯生成片段也可以是一個簡短的C代碼塊,但最終也會返回一個包含有效組譯代碼的字串。
GNU除錯器是一個為GCC除錯的程式。其他特殊用途的除錯工具是Valgrind,用以發現記憶體流失(memory leak)。而GNU測量器(gprof)可以得知程式中某些函式花費多少時間,以及其呼叫頻率;此功能需要使用者在編譯時選定測量(profiling)選項。
內嵌組譯也稱行內組譯,是把組合語言代碼塊插在C語言語句之間。詳情參見GCC-Inline-Assembly-HOWTO.html(頁面存檔備份,存於網際網路檔案館)