首页 > 编程知识 正文

编译器的设计与实现,gcc编译器安装

时间:2023-05-03 15:41:30 阅读:120331 作者:210

GNU/GCC编译过程可以细分为以下四个阶段:

预处理(预处理)

编译(Compiling )

汇编(Assembling ) )

链接链接

Linux程序员可以根据需要在编译的任何阶段退出GCC,以检查和使用编译器在该阶段的输出信息,控制最后生成的二进制文件,并通过添加不同数量和类型的调试代码来为以后的调试做准备与其他常见编译器一样,GCC也提供了灵活、强大的代码优化功能,可以生成更高效的代码。

gcc编译器---前端和后端

GCC编译器由编译预处理组件的cpp和用于特定语言的编译器cc1组成

[ xxxx @ test _ paag ] $ gccs-O2 main.c-omain.s

解释此编译命令:命令行GCC接受并解释命令行参数,根据命令行参数的解释结果选择下一步,最后调用编译组件cc1进行编译和汇编

gcc编译器的工作过程

GCC编译器前端将高级语言源代码经过词法分析、语法分析生成与高级语言无关的低级中间层表示GENERIC[36],经过单一化赋值转换为另一个中间表示层GIMPLE[36],由中间层GIMPLE控制流程构建CFG,在GIMPLE上执行,然后将其转换为更容易优化的注册传输语言(RTL )中间表示层[37],在RTL层上执行多次优化路径,最后返回RTL GCC编译流程图如下图所示。

前端

GCC编译器的前端非常强大,经过多年的发展,GCC编译器的前端已经相当成熟,可以编译目前计算机界主流的高级编程语言,前端有c、Fortran、Pascal

GCC编译的前端将不同的高级编程语言经过词法分析、语法分析转换为与前端语言无关的统一的中间表示。 存在与前端无关的中间表示,GCC前端将不同的高级编程语言转换为该中间表示,这是GCC处理器支持多种编程语言的根本原因。

后端

编译器分为前端和后端,在GCC配置中,后端负责优化由RTL表示的中间格式,最终生成与平台对应的汇编代码。 GCC后端以RTL的中间格式进行,许多优化都在RTL中进行,包括计算机优化和独立于计算机的优化。

RTL是从特定机器平台抽象出来的模型,实际上是抽象出来的机器模型。 和所有实际存在的平台一样,这个抽象的机器模型也有自己的“命令系统”。 机器模型的“命令系统”在GCC中被定义为标准命令模板的一系列命令,这些命令在GCC内部使用。 GCC后端为不同的平台提供了描述这些命令模板的接口文件。 这些命令模板在不同平台上的描述将GCC映射到不同的平台,使GCC的后端可以移植到不同的平台。 通过机器描述支持多种体系结构,机器描述文件提供机器各种参数的宏定义和指令集。 这使GCC移植变得非常容易,并为当前广泛使用的目标计算机体系结构提供了大部分支持。 后端支持30多个平台,包括x86、mips、alpha、ARM、AVR、IA-64、SPARC和PowerPC。

gcc编译器------前后端纽带:路径管理器

GCC编译器前端对高级语言源代码进行词法分析、语法分析,生成与高级语言无关的低级中间层表示,后端优化RTL表示的中间形式,最终生成相应平台的汇编代码。 从GCC的前端到后端共有3种中间表示,GCC通过路径管理器连接在一起。 在编译和优化过程中,GCC对编译目标(通常处理函数或文件)的单次编译过程称为过程(pass )路径。 GCC的整个编译处理过程构成pass_list,该pass_list中包含的所有路径都是GCC编译时的整个过程。 路径管理器在GCC编译过程中的作用如下图所示。

GCC编译器前端对源语言进行词法分析、句法分析后得到抽象语法树(AST ),该过程为通用。 然后通用简化为GIMPLE的中间表示,在GIMPLE层加入控制流图(CFG ),在该中间表示层进行一系列的先行优化(例如过程间优化、静态单一化赋值SSA等),将GIMPLE变换为RTL中间表示从最终生成汇编代码的GENERIC到GIMPLE再到RTL,GCC中用一系列的路径连接(图1中用箭头的虚线表示) ) ) ) ) ) ) ) ) )

个 pass_list。

gcc编译器----中间表示

中间表示( intermediate representation, IR) 指编译器对于源程序进行扫描后生成的内部表示,代表源程序的语义和语法结构,编译器的各个阶段都在IR上进行分析或优化变换,因而它对编译器的整体结构、效率和健壮性都有着极大的影响[38]。中间表示对提高编译器的可移植性以及代码生成起到关键作用,在编译器的研究中,应该设计一种结构良好的中间表示,这种中间表示应在适当的抽象层次上,向上能支持多语言的映射,向下能适应多平台转换且易于进行各种优化。

对于现代编译器来说,编译器前端和后端分别指分析输入源语言和生成目标平台汇编代码的两编译阶段,大部分现代编译器在前端和后端之间会有个中间表示层。有了一个设计良好的中间表示,有m种前端编程语言(CC++、Fortran、Ada、Java等)和n种后端平台(x86、MIPS、Sparc、ARM等)的编译器设计,这样就减少了为了设计m中语言和n个平台从而设计m*n个编译器设计。


     中间语言的使用可以大大减低开发各个语言和各个平台所需编译器的工作量。支持多种编程语言和多种后端平台的编译器可以通过使用一种中间表示来实现。总的来说,中间表示层在减低编译器开发方面的开销和复杂度上功不可没。有些中间表示语言的设计是专门为了支持一种特定的语言,例如Jvm的设计仅仅是为了支持java语言,而大多数中间表示语言的设计是用来将不同的前端语言和后端平台连接起来。

       GCC编译器有三种中间表示语言,一种AST/GENERIC比较完善的表示了前端语言的信息,一种GIMPLE用来在相对比较高的层次来表示源语言程序。而另外一种rtl用来高度抽象的表示从特定平台抽象出来的机器指令。

        一、GENERIC形式与前端的编程语言是相关的,每种前端语言词法语法分析后形成的AST/GENERIC是异构的,GENERIC包含了前端语言所有的信息。

GCC编译器的前端将高级语言源码经过词法分析、语法分析生成GENERIC。GENERIC是一棵抽象语法树(Abstract Syntax Tree,AST),用数据结构中的树(tree)结构表示。GCC编译器的抽象语法树是源程序的一种中间表示形式,比较直观的表示出源程序的语法结构,并含有源程序结构显示所需要的全部静态信息。GCC 格式的 AST 文件是 GCC 编译源程序时产生的,以文本方式记录源程序抽象语法树的文件。

C语言源码和对应的GENERIC表示如下所示:

C语言源码:

int sum(int a,int b){  int c; c=a+b; return c;}


GENERIC表示(部分):

 

GCC的前端将源语言解析生成GENERIC。不同的前端高级编程语言,GCC前端生成的GENERIC形式不尽相同。每种前端语言词法语法分析后形成的AST/GENERIC是异构的,需要转换成一种统一的中间形式进行后续的处理,这种统一的中间表示形式就是GIMPLE形式。
(关于 GIMPLE 命名是从编译器 SIMPLE IL 改造合成而来)

二、中间表示GIMPLE:GIMPLE是一种三地址码的中间表示形式。

GIMPLE中间形式由GENERIC表达式变换而来,与GENERIC相比,主要有如下的转换:

(1)、通过引入临时变量保存中间结果,并将GENERIC表达式拆分成不超过三个操作数的元组(tuples)。

(2)、GENERIC中的控制结构,例如if-else,for,while等也被转换成条件转移。

(3)、词法作用域(lexical scopes)被取消了。

(4)、异常区域(lexical scopes)被转换成一个单独的异常区域树(exception region tree)。

GIMPLE是通过简化GENERIC得来的,这样做的好处通过下面的例子比较来说明。

xxx@localhost$ cat compare_generic_to_gimple.c

int func(int j, int k) {int i;for (i=0;i>=10;i++) {j = i + 1; k = j + i + 5;}return k;}int func(int j, int k) {int i;for (i=0;i>=10;i++) {j = i + 1;  k = j + i + 5;}return k;}


使用如下命令进行编译,其中-fdump-tree-gimple选项打印出GCC处理的中间过程,包括列出GENERIC的中间形式和GIMPLE中间形式。

 xxx@localhost$ paag-linux-gcc -fdump-tree-gimple -S -o compare_generic_to_gimple.s compare_generic_to_gimple.c
 xxx@localhost$ paag-linux-gcc -fdump-tree-gimple -S -o compare_generic_to_gimple.s compare_generic_to_gimple.c


下表分别给出其GENERIC形式和GIMPLE表示,并进行对比分析。

 

从上图可以看出,GIMPLE相比较GENERIC而言的优势,也就是为什么要进行GENERIC形式到GIMPLE形式的转化,主要有以下几个方面的原因:

(1) GENERIC是树结构表示和存储的,对于分析和处理非常不便。

(2) GENERIC形式与前端的编程语言是相关的,而GIMPLE是与前端编程语言无关的,所有的语言并不能够表示成统一的GNERIC,这就使得GENERIC并不适合做优化

(3) GIMPLE形式从本质上讲就是线性的代码序列,所有的计算表达式都表示成一系列的基本操作。这样可以更方便有效地进行后续的编译优化,优化算法很容易在GIMPLE形式上实现。在GIMPLE的表示中,控制结构都分解为直接或间接跳转语句。

(4) GIMPLE表达式的表现形式更加严谨,除了函数表达式以外,每个表达式的操作数不超过3个。

(5) GIMPLE表达式将GENERIC分解为3地址格式的表达式,用临时变量来存储计算中的中间值。同样在GIMPLE的表示中,GIMPLE节点并不表示其节点的值,类似CON_EXPR或是BIND_EXPR这样的节点如果有值,那么GIMPLE格式的表示会将其值存储在临时变量中。

   GIMPLE形式的中间表示由于其与前端无关的特性,将多种前端高级编程语言统一到一种中间表示上来,并且在GIMPLE层上做了部分优化。随后GCC将GIMPLE转换成RTL形式的中间表示。

三、中间表示RTL:RTL叫做“寄存器转移语言”(Register Transfer Language),它是以一种虚拟寄存器(pseudo register)的方式来叙述计算机行为的语言。RTL 是一种接近机器指令的语言,既有指令序列组成的内部形式,又有机器描述和调试信息组成的文本形式。

RTL的表示形式:RTL的产生受到了LISP表的启发,要描述的输出指令基本上都是以字母表顺序一个接一个地描述指令完成的工作。它既有内部(内存)形式,是由指向其他结构的指针组成;也有文本形式,用于机器描述和调试输出打印。

RTL的对象类型:表达式,整数,宽整数,字符串和向量。其中表达式是最重要的一类对象。一个RTL表达式类似于一个c的结构体,通常用指针来引用它,指针类型定义为RTX,每个RTX都具有自己的内部数据结构与外部语法。

RTL在一个指令序列(INSN)中如下图 所示。


在GCC中,函数代码的RTL表示被称为INSNs,所有的INSNs被一个双向链表所连接(注意,不是循环双向链表)。有些INSN表示实际的指令,有些代表switch语句的跳转表,有些表示程序跳转所对应的标号,还有一些可以表示各种不同的声明信息。INSN的格式为:

(insn 第0个操作数   第1个操作数  第2个操作数  第3个操作数  第4个操作数 第5个操作数 第6个操作数 第7操作数)
 

经过上面对 GCC 编译器的整体结构分析可以总结得出 GCC 编译器中主要包含三种中间语言表示形式,分别为 GENERIC层,GIMPLE层(包含高级 GIMPLE 和低级 GIMPLE 以及 SSA)和 RTL表示。这三种中间语言都是与前端语言无关的,其中 GENERIC 树是将前端语言直接翻译过来后形成的中间树,而GIMPLE 则是简化了的 GENERIC 树的集合,在将 GENERIC 转化为 GIMPLE 的过程中将 GENERIC 树中比较复杂的语句都转化为了多个比较简单的语句,其中的计算结果用临时变量来保存RTL是通过低级GIMPLE转化而来的,将低级GIMPLE 树转化为 RTL 树的主要目的是进行优化。

参考:

若想了解 gcc 编译器细节可参考官方文档:

​​​​​​GCC online documentation- GNU Project

Top (GNU Compiler Collection (GCC) Internals)

Top (The GNU C Preprocessor Internals)

https://Blog.csdn.net/u012491514/article/details/24736041

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。