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(页面存档备份,存于互联网档案馆)