初识GCC

来源:互联网 发布:大数据产业测度 编辑:程序博客网 时间:2024/06/05 22:35

            本文仅限于初步学习gcc者,要是对gcc有一定的深入学习了,效果就没有那么大了。

概述

GCC(GNU Compiler Collection,GNU编译器套装),是一套由GNU开发的编程语言编译器。它是一套以GPL及LGPL许可证所发行的自由软件,也是GNU计划的关键部分,亦是自由的类Unix及苹果电脑Mac OS X 操作系统的标准编译器。GCC(特别是其中的C语言编译器)也常被认为是跨平台编译器的事实标准,它是移植到中央处理器架构以及操作系统最多的编译器。 

GCC原名为GNU C语言编译器(GNU C Compiler),因为它原本只能处理C语言。GCC很快地扩展,变得可处理C++。之后也变得可处理Fortran、Pascal、Objective-C、Java、Ada,以及Go与其他语言。

GCC的架构

GCC可简单地分为2部分:前端和后端。预处理器(如果存在)、词法分析器和语法分析器在前端实现,前端的作用是将源语言写的程序转换成与语言无关的中间形式。因此,理论上,引入对新语言的支持,只需要实现预处理器、词法分析器和语法分析器。但实际上,一般的我们还需要写一些代码以确立运行时环境。

在GCC 3.4.6,这个通用的中间语言是RTL(register transfer language)。RTL是一种简单的语言,很容易就能翻译成汇编代码。因此,直接将源语言转换为RTL,不太合适。事实上,前端首先会将代码转换成中间树并进行大量的处理,然后再进一步变换为RTL,并送入后端。

后端的作用则是生成汇编代码。作为广泛使用的编译器,GCC可以支持各种流行的平台。为了实现这个目的,GCC采用了机器描述文件,对目标机器的指令集、流水线结构、甚至架构本身带来的优化机会进行描述。对于目标机器(配置GCC时需指定),这些描述文件将被多个工具处理,生成相应的源代码,然后用于编译GCC。因此,引入新机器的主要工作,在于提供机器描述文件(一般而言还需要定义一些处理函数提供必要的处理逻辑)。

前端接口

前端的功能在于产生一个可让后端处理之语法树。此语法解析器是手写之递回语法解析器。

直到最近,程序的语法树结构尚无法与欲产出的处理器架构脱钩。而语法树的规则有时在不同的语言前端也不一样,有些前端会提供它们特别的语法树规则。

在2005年,两种与语言脱钩的新型态语法树纳入GCC中。它们称为GENERIC与GIMPLE。语法解析变成产生与语言相关的暂时语法树,再将它们转成GENERIC。之后再使用"gimplifier"技术降低GENERIC的复杂结构,成为一较简单的静态唯一形式(Static Single Assignment form,SSA)基础的GIMPLE形式。此形式是一个与语言和处理器架构脱钩的全域最佳化通用语言,适用于大多数的现代编程语言。

中介接口

一般编译器作者会将语法树的优化放在前端,但其实此步骤并不看语言的种类而有不同,且不需要用到语法解析器。因此GCC作者们将此步骤归入通称为中介阶段的部分里。此类的优化包括消解死码、消解重复运算与全局数值重编码等。许多优化技巧也正在实现中。  

后端接口

GCC后端的行为因不同的前处理器宏和特定架构的功能而不同,例如不同的字符尺寸、呼叫方式与大小尾序等。后端接口的前半部利用这些讯息决定其RTL的生成形式,因此虽然GCC的RTL理论上不受处理器影响,但在此阶段其抽象指令已被转换成目标架构的格式。

GCC的最佳化技巧依其释出版本而有很大不同,但都包含了标准的最佳化算法,例如循环最佳化、执行绪跳跃、共通程序子句消减、指令排程等等。而RTL的最佳化由于可用的情形较少,且缺乏较高阶的资讯,因此比较起近来增加的GIMPLE语法树形式,便显得比较不重要。

后端经由一重读取步骤后,利用描述目标处理器的指令集时所取得的资讯,将抽象暂存器替换成处理器的真实暂存器。此阶段非常复杂,因为它必须关照所有GCC可移植平台的处理器指令集的规格与技术细节。

后端的最后步骤相当公式化,仅仅将前一阶段得到的组合语言码藉由简单的副函式转换其暂存器与内存位置成相对应的机械码。 

替 GCC 程序除错

为 GCC 除错的首选工具当然是 GNU 除错器。其他特殊用途的除错工具是 Valgrind, 用以发现内存漏失 (Memory leak)。而 GNU 测量器 (gprof) 可以得知程序中某些函式花费多少时间,以及其呼叫频率;此功能需要使用者在编译时选定测量〈profiling〉选项。

gcc所遵循的部分约定规则: 

  .c为后缀的文件,C语言源代码文件; 

  .a为后缀的文件,是由目标文件构成的档案库文件; 

  .cc或.cxx 为后缀的文件,是C++源代码文件; 

  .h为后缀的文件,是程序所包含的头文件; 

  .i 为后缀的文件,是已经预处理过的C源代码文件; 

  .ii为后缀的文件,是已经预处理过的C++源代码文件; 

  .m为后缀的文件,是Objective-C源代码文件; 

  .o为后缀的文件,是编译后的目标文件; 

  .s为后缀的文件,是汇编语言源代码文件; 

.S为后缀的文件,是经过预编译的汇编语言源代码文件。 

GCC最基本的用法是∶gcc [options] [filenames] 命令行选项制定操作将对命令行上的每个给出的文件执行

    其中options就是编译器所需要的参数,filenames给出相关的文件名称。 

    -c,只编译,不连接成为可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件。 

    -o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。 

    -g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。 

        -pg选项告诉gcc在用户的程序中加入额外的代码,执行时,产生gprof用的剖析信息以显示程序的耗时情况

    -O,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。 

    -O2,比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。 

    -Idirname,将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数。C程序中的头文件包含两种情况∶ 

    A)#include <myinc.h> 

    B)#include “myinc.h” 

    其中,A类使用尖括号(< >),B类使用双引号(“ ”)。对于A类,预处理程序cpp在系统预设包含文件目录(如/usr/include)中搜寻相应的文件,而B类,预处理程序在目标文件的文件夹内搜索相应文件。 

注解:优化选项:用GCC编译C/C++代码时,它会试着用最少的时间完成编译并且编译后的代码易于调试。易于调试意味着编译后的代码与源代码有同样的执行顺序,编译后的代码没有经过优化。

gdb使用方法:在命令行中健入gdb并按回车就可以运行gdb了,启动gdb后,能在命令行上制定很多的选项,也可以下面的方式来运行gdb: gdb filename 用这种方式运行gdb时,能直接指定想要调试的程序。在命令行上健入gdb -h得到一个有关gdb的选项的说明简单列表。 

编译代码以供调试,为了使gdb工作,必须使程序在编译时包含调试信息,调试信息包含程序里的每个变量的类型,在可执行文件里的地址映射以及源代码的行号。gdb利用这些信息使源代码和机器码相关联。

看下面的例子:

例一:test.c

#include

main()

{ char *str="I like Linux! I advices you jion in the Linux World";

printf("%s ",str);

exit(0);

}

使用gcc编译。

输入gcc -c test.c得到目标文件test.o.(-c命令表示对文件进行编译和汇编。)但并不连接。

如果再健入gcc -o ../bin/test test.o,那么将得到名为test的可执行文件。

其实这两不可以一气呵成,gcc ../bin/test test.c.如果程序没有错误就生成了可执行文件。

执行程序:gcc test.c -o test 即可。

执行过程理论:

 使用gcc由C语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。 

命令gcc首先调用源文件进行预处理,在预处理过程中,对源代码文件中的文件包含(include)、预编译语句(如宏定义define等)进行分析。接着调用cc1进行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。汇编过程是针对汇编语言的步骤,调用as进行工作。当所有的目标文件都生成之后,gcc就调用ld来完成最后的关键性工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的档案库中连到合适的地方。但GCC 的实际操作上,它可以把这三个步骤合并为一个步骤来执行。

下面我们以C语言为例来谈一下不同阶段的输入和输出情况。 

在预处理阶段,输入的是C语言的源文件, 通常为*.c。它们通常带有.h之类头文件的包含文件。这个阶段主要处理源文件中的#ifdef、 #include和#define命令。该阶段会生成一个中间文件*.i,但实际工作中通常不用专门生成这种文件,因为基本上用不到;若非要生成这种文件 不可,可以利用下面的示例命令: 

gcc -E test.c -o test.i 

在编译阶段,输入的是中间文件*.i,编 译后生成汇编语言文件*.s 。这个阶段对应的GCC命令如下所示: 

GCC -S test.i -o test.s 

在汇编阶段,将输入的汇编文件*.s转换 成机器语言*.o。这个阶段对应的GCC命令如下所示: 

GCC -c test.s -o test.o 

最后,在连接阶段将输入的机器代码文件*.s(与其它的机器代码文件和库文件)汇集成一个可执行的二进制代码文件。这一步骤,可以利用下面的示例命令完成: 

GCC test.o -o test 

注意:要生成可执行程序时,一个程序无论有有一个源文件还是多个源文件,所有被编译和连接的源文件中必须有且仅有一个main函数,因为main函数是该程序的入口点(换句话说,当系统 调用该程序时,首先将控制权授予程序的main函数)。但如果仅仅是把源文件编译成目标文件的时候,因为不会进行连接,所以main函数不是必需的。 

0 0
原创粉丝点击