gcc编译器功能强大,包括警告提示功能、代码优化功能、链接库、使用管道加速等,下面通过一个个具体的实例来一一介绍这些功能。
GCC编译初步
如前面所述,gcc的编译过程分为预处理、编译、汇编、链接4个阶段。从功能上分,预处理、编译、汇编是3个不同的阶段,但gcc在实际操作上,可以吧这3个步骤合并为一个步骤来执行。下面通过简单的Hello world的例子来讲解gcc的工作原理。
首先用vi编辑器键入一下代码并保存为hello.c
然后输入下面的命令编译和运行这段程序:
运行上述命令过后,我们可以发现编译器自动创建了一个a.out的输出文件,而“./”表示执行当前目录下的可执行程序或脚本程序。当然,用户可以使用gcc的选项“-o”来改变编译后的文件名,如使用下面的命令,将输出文件命名为“hello”,执行hello文件之后可以得到相同的结果
现在我们回到编译过程中,从程序员的角度来看,只需要简单的一条gcc命令就可以了,但从编译器的角度来看,却需要完成一系列非常繁杂的工作。
- 首先,gcc需要调用预处理程序cpp,由它复杂展开在源文件中定义的宏,并向其中插入“#include”语句所包含的内容
- 接着,gcc会调用ccl和as将处理后的源代码编译成目标代码;
- 最后gcc,会调用链接程序ld,把生成的目标代码链接成一个可执行程序。
为了更好的理解gcc的处理过程,可以把以上编译过程分成几个步骤单独进行,并观察每一步之后的结果:
(1)预处理。在预处理阶段,gcc吧预处理命令扫描处理完毕,输入C语言的源文件,通常为*.c,他们通常带有.h之类的包含文件。这个阶段主要处理源文件中的#ifdef、#include、#define等预处理命令。该阶段会生成一个中间文件*.i,但实际工作中通常不会专门生成这类文件,因为基本上都用不到,若非要生成这种文件,可以使用-E参数让gcc在预处理结束后停止编译过程。
此时会发现头文件中的stdio.h的内容已经被插入到文件中去了,而其他应当被预处理的宏定义也都做了相应的处理
(2)编译阶段:在编译阶段,gcc把预处理后的结果编译成汇编或目标模块。输入的是中间文件*。i在编译后生产汇编语言文件*.s。这个阶段对应的gcc命令如下:
(3)汇编:在汇编阶段,编译器会把编译出来的结果汇编成具体CPU上的目标代码模块。输入汇编文件*.s,输出机器语言*.o。这个阶段可以通过是哟-c参数来实现
(4)链接:在链接阶段吧多个目标代码模块链接生成一个大的目标模块。输入机器代码文件*.o(与其他的既期待吗文件和库文件),汇集成一个可执行的二进制代码文件,这一步骤可以通过以下代码实现:
运行hello
警告显示显示功能
gcc包含完整的出错检查和警告提示功能,它们可以帮助LInux程序员写出更加专业和优美的代码。
-pedantic编译选项并不能保证被编译的程序与ANSI/ISO C标准的完全兼容,它只能用来帮助Linux程序员离这个目标越来越近,换句话说,-pedantic选项能够帮助程序员发现一些不符合ANSI/ISO C标准的代码,但不是全部。事实上只有ANSI/ISOC语言标准中要求进行编译器诊断的哪些情况,才有可能被gcc发现并提出警告。
除-pedantic之外,gcc还有一些其他编译选项也能够产生有用的警告信息。这些选项大多数都是有-W开头,其中最有价值的是-Wall使用它能够使gcc产生尽可能多的警告信息。
gcc给出的警告信息虽然从严格意义上来说不能算错误,但是却很可能成为错误的栖身之所。一个优秀的Linux程序员应该尽量避免产生警告信息,是自己的代码始终保持简洁、优美和健壮的特性。
优化gcc
代码的优化功能是线代C编译器一个最令人心动的特性,作为Linux平台下的标准C编译器,gcc拥有强大并且可配置的优化器,从而能够对程序进行优化处理。
代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新组合,目的是改善程序的执行性能。gcc提供的代码优化功能非常强大,它通过编译选项-On来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的gcc来说,n的取值范围以及对应的优化效果可能并不相同,比较典型的范围是从0变到2或3.
编译时使用选项-O可以告诉gcc同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行优化类型虽然取决于目标处理器,但一般都会包括线程跳转和延迟退栈两种优化。
-O2选项告诉gcc除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,比如处理器指令调度等。
选项-O3告诉gcc除了完成所有-O2的优化之外,同时还包括循环展开和其他一些与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。许多Linux程序员都比较喜欢使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。
同时编译多个源程序
在采用模块化设计思想进行软件开发时,通常整程序是由多个源文件组成的。也就相应的形成了多个编译单元,使用gcc能够很好的管理这些编译单元。假设有一个由fool1.c\foo2.c\foo3.c三个源文件组成的程序,为了对他们进行编译,并最终生成一个可执行文件,可以使用下面这条命令:
gcc foo1.c foo2.c foo3. c -o foo
管道
Linux提供了一种高效的通信方式——管道(pipe).管道的实质是进程间的通信方式,在这里简单地说,它可以用来同时连接两个程序(进程),其中一个程序的输出程序将被直接作为另外一个程序的输入,这样就可以避免使用临时文件,但编译时却需要消耗更多的内存。
在编译过程中使用管道是由gcc的pipe选项决定的。下面这条命令就是借助gcc的管道功能来提高编译速度的:
gcc -pipe -Wall foo.c -o foo
调试选项
在默认情况下,gcc在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执行文件的大小。如果需要在编译时生成调试符号信息,可以使用gcc的-g选项或者-ggdb选项。
gcc在产生调试符号时,同样采用了分级的思路,开发人员可以通过在-g选项后附加数字1,2,3来指定在代码中加入调试信息的多少。默认级别是2(-g2),此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息。级别3(-g3)包含级别2中的所有调试信息,以及源代码和宏。
加入调试选项比不加入调试选项生成的文件要大。