首页 理论教育 GCC编译的基本流程-Linux系统编程

GCC编译的基本流程-Linux系统编程

时间:2023-11-16 理论教育 版权反馈
【摘要】:除了-pedantic之外,GCC还有一些其他编译选项也能够产生有用的警告信息。建议在用GCC编译源代码时始终带上-Wall选项,并把它逐渐培养成为一种习惯,这对找出常见的隐式编程错误很有帮助。默认情况下,GCC在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执行文件的大小。

GCC编译的基本流程-Linux系统编程

1.C预处理

C预处理器CPP是用来完成对于程序中的宏定义等相关内容进行先期的处理。一般是指那些前面含有“#”号的语句,这些语句一般会在CPP中处理。例如:

#define MR(25*4)

printf("%d",MR*5);

经过CPP的处理后,就会变成如下格式传递到代码中:

printf("%d",(25*4)*5);

其实不难看出,CPP的作用就是解释后定义和处理包含文件。在使用时,GCC会自动调用CPP预处理器。

2.编译

编译的过程就是将输入的源代码和预处理相关文件编译为“.o”的目标文件。

3.汇编

在使用GCC编译程序的时候,会产生一些汇编代码,而处理这些汇编代码就需要使用汇编器as,as可以处理这些汇编代码,从而使其成为目标文件,最终目标文件转换成.o文件或其他可执行文件。而且as汇编器和CPP一样,可以被GCC自动调用。

4.链接

在处理一个较大的C语言项目时,我们通常会将程序分割成很多模块,那么这时候就需要使用链接器将这些模块组合起来,并且结合相应的C语言函数库和初始代码,产生最后的可执行文件。链接器一般用在一些大的程序和项目中,对最后生成可执行文件起着重要的作用。

虽然GCC可以自动调用链接器,但是为了更好地控制连接过程,建议最好手动调用连接器。

5.警告

GCC包含完整的出错检查和警告提示功能,它们可以帮助Linux程序员写出更加专业和优美的代码。先来看看下面所示的程序,这段代码写得很有问题,仔细检查一下不难挑出很多毛病:

(1)main函数的返回值被声明为void,但实际上应该是int。

(2)使用了GNU语法扩展,即使用long long来声明64位整数,不符合ANSI/ISO C语言标准。

(3)main函数在终止前没有调用return语句。

下面来看看GCC是如何来发现这些错误的。当GCC在编译不符合ANSI/ISO C语言标准的源代码时,如果加上了-pedantic选项,那么使用了扩展语法的地方将产生相应的警告信息:

# gcc -pedantic illcode.c -o illcode illcode.c:In function `main':illcode.c:9:ISO C89 does not support `long long' illcode.c:8:return type of `main' is not `int'(www.xing528.com)

值得注意的是,-pedantic编译选项并不能保证被编译程序与ANSI/ISO C标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近。换句话说,-pedantic选项能够帮助程序员发现一些不符合 ANSI/ISO C标准的代码,但不是全部,事实上只有ANSI/ISO C语言标准中要求进行编译器诊断的那些情况,才有可能被GCC发现并提出警告。

除了-pedantic之外,GCC还有一些其他编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall,使用它能够使GCC产生尽可能多的警告信息:

# gcc -Wall illcode.c -o illcode illcode.c:8:warning:return type of `main' is not `int'illcode.c:In function `main':illcode.c:9:warning:unused variable `var'

GCC给出的警告信息虽然从严格意义上说不能算作是错误,但却很可能成为错误的栖身之所。作为一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持简洁、优美和健壮的特性。

在处理警告方面,另一个常用的编译选项是-Werror,它要求GCC将所有的警告当成错误进行处理,这在使用自动编译工具(如Make等)时非常有用。如果编译时带上-Werror选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。只有当相应的警告信息消除时,才可能将编译过程继续朝前推进。执行情况如下:

# gcc -Wall -Werror illcode.c -o illcode cc1:warnings being treated as errors illcode.c:8:warning:return type of `main' is not `int' illcode.c:In function `main':illcode.c:9:warning:unused variable `var'

对Linux程序员来讲,GCC给出的警告信息是很有价值的,它们不仅可以帮助程序员写出更加健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用GCC编译源代码时始终带上-Wall选项,并把它逐渐培养成为一种习惯,这对找出常见的隐式编程错误很有帮助。

6.GCC调试

一个功能强大的调试器不仅为程序员提供了跟踪程序执行的手段,而且还可以帮助程序员找到解决问题的方法。对于 Linux程序员来讲,GDB(GNU Debugger)通过与GCC的配合使用,为基于Linux的软件开发提供了一个完善的调试环境

默认情况下,GCC在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执行文件的大小。如果需要在编译时生成调试符号信息,可以使用GCC的-g或者-ggdb选项。GCC在产生调试符号时,同样采用了分级的思路,开发人员可以通过在-g选项后附加数字1、2或3来指定在代码中加入调试信息的多少。默认的级别是2(-g2),此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息。级别3(-g3)包含级别2中的所有调试信息,以及源代码中定义的宏。级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段。

GCC产生的调试符号具有普遍的适应性,可以被许多调试器加以利用,但如果使用的是GDB,那么还可以通过-ggdb选项在生成的二进制代码中包含GDB专用的调试信息。这种做法的优点是可以方便GDB的调试工作,但缺点是可能导致其他调试器(如DBX)无法进行正常的调试。选项-ggdb能够接受的调试级别和-g是完全一样的,対它们输出的调试符号有着相同的影响。

值得注意的是,使用任何一个调试选项都会使最终生成的二进制文件的大小急剧增加,同时增加程序在执行时的开销,因此调试选项通常仅在软件的开发和调试阶段使用。调试选项对生成代码大小的影响可以从下面的对比过程中看出:

#gcc optimizec.c -o optimize

#Is optimize-1 -rwxrwxr-x 1 xiaowp xiaowp 11649 Nov 2008:53 optimize (未加调试选项)

# gcc -g optimize.c -o optimize

#Is optimize-1-rwxrwxr-x 1 xiaowp xiaowp 15889 Nov 20 08:54 optimlze(加入调试选项)

虽然调试选项会增加文件的大小,但事实上 Linux中的许多软件在测试版本甚至最终发行版本中仍然使用了调试选项来进行编译,这样做的目的是鼓励用户在发现问题时自己动手解决,这是Linux的一个显著特色。

下面还是通过一个具体的实例说明如何利用调试符号来分析错误,所用程序如下所示:

7.代码优化

代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,目的是改善程序的执行性能。GCC提供的代码优化功能非常强大,它通过缩译选项-On来控制优化代码的生成,其中n是一个代表优化级别的整数。对编译时使用选项-O可以告诉GCC同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉GCC除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3则除了完成所有O2级别的优化之外,还包括循环展开和其他与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。许多Linux程序员都喜欢使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈