文本输入模式(Input Mode)
3.4 编译器 GCC 的使用
3.4.2 GCC 编译选项解析
GCC 是 Linux 下基于命令行的 c 语言编译器,其基本的使用语法如下。
gcc [option | filename ]…
对于编译 C++的源程序,其基本的语法如下。
g++ [ option | filename ]…
其中 option 为 GCC 使用时的选项(后面会再详述),而 filename 为需要用 GCC 作编译 处理的文件名。就 GCC 来说,其本身是一个十分复杂的命令,合理地使用其命令选项可以 有效提高程序的编译效率、优化代码,GCC 拥有众多的命令选项,有超过 100 个的编译选项 可用,按其应用有如下的分类。
1.常用编译选项
• -c 选项:这是 GCC 命令的常用选项。-c 选项告诉 GCC 仅把源程序编译为目标代码 而并不做链接的工作,所以采用该选项的编译指令不会生成最终的可执行程序,而是生成一 个与源程序文件名相同的以.o 为后缀的目标文件。例如一个 Test1.c 的源程序经过下面的编译 之后会生成一个 Test1.o 的文件。
# gcc –c Test1.c
• -S 选项:使用该选项会生成一个后缀名为.s 的汇编语言文件,但是同样不会生成可执 行的程序。
• -e 选项:-e 选项只对文件进行预处理,预处理的输出结果被送到标准输出(比如显 示器)。
• -v 选项:在 Shell 的提示符号下键入 gcc -v,屏幕上就会显示出目前正在使用的 GCC 的版本信息。例如:
# gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux7/2.96/specs gcc version 2.96 20000731(Redhat linux 7.3 2.96-128)
上面的系统信息指出了 GCC 的版本:gcc 2.96。
• -x language:强制编译器用指定的语言编译器来编译某个源程序。
例如下面的指令:
# gcc –x c++ P1.c
该指令表示强制采用 C++编译器来编译 C 程序 P1.c。
• -I<DIR>选项:库依赖选项,指定库及头文件路径。
在 Linux 下开发程序的时候,通常来讲都需要借助一个或多个函数库的支持才能够完成 相应的功能。一般情况下,Linux 下的大多数函数都将头文件放到系统/usr/include/目录下,
而库文件则放到/usr/lib/目录下。但在有些情况下并不是这样的,在这些情况下,使用 GCC
tyw藏书
编译时必须指定所需要的头文件和库文件所在的路径。-I 选项可以向 GCC 的头文件搜索路径 中添加新的目录<DIR>。例如,一个源程序所依赖的头文件在用户/home/include/目录下,此 时就应该使用-I 选项来指定。
# gcc -I /home/include -o Test Test.c
• -L<DIR>:类似上面的情况,用来特别指定所依赖库所在的路径。
如果使用了不在标准位置的库,那么可以通过-L 选项向 GCC 的库文件搜索路径中添加 新的目录。例如,一个程序要用到的库 libapp.so 在/home/zxq/lib/目录下,为了能让 GCC 能 够顺利地链接该库,可以使用下面的命令:
#gcc –Test.c –L /home/zxq/lib –lapp –o Test
这里的-L 选项表示 GCC 去连接库文件 libapp.so。Linux 下的库文件在命名时有一个约定,
那就是应该以 lib 三个字母开头,由于所有的库文件都遵循了同样的规范,因此在用-L 选项 指定链接的库文件名时可以省去 lib 三个字母,也就是说 GCC 在对-lapp 进行处理时,会自动 去链接名为 libapp.so 的文件。
• -static 选项:GCC 在默认情况下链接的是动态库,有时为了把一些函数静态编译到程 序中,而无需链接动态库就采用-static 选项,它会强制程序链接静态库。
• -o 选项:在默认的状态下,如果 GCC 指令没有指定编译选项的情况下会在当前目录下 生成一个名位 a.out 的可执行程序,例如:执行# gcc Test.c 命令之后会生成一个 a.out 的可执行 程序。因此,为了指定生成的可执行程序的文件名,就可以采用-o 选项,比如下面的指令:
# gcc –o Test Test.c
执行该指令会在当前目录下生成一个名为 Test 的可执行文件。
注意 使用-o 选项时,-o 后面必须带有可执行文件的文件名(可以任意指定)。
2.出错检查和警告提示选项
GCC 编译器包含完整的出错检查和警告提示功能,比如 GCC 提供了 30 多条警告信息和 3 个警告级别,使用这些选项有助于增强程序的稳定性和更加完善程序代码的设计,此类选 项常用的如下。
• -pedantic 以 ANSI/ISO C 标准列出的所有警告
当 GCC 在编译不符合 ANSI/ISO C 语言标准的源代码时,如果在编译指令中加上了 -pedantic 选项,那么源程序中使用了扩展语法的地方将产生相应的警告信息。
• -w 禁止输出警告消息
• -Werror 将所有警告转换为错误
Werror 选项要求 GCC 将所有的警告当成错误进行处理,这在使用自动编译工具(如 Make 等)时非常有用。如果编译时带上-Werror 选项,那么 GCC 会在所有产生警告的地方停止编 译,只有程序员对源代码进行修改并且相应的警告信息消除时,才可能继续完成后续的编译
tyw藏书
工作。
• -Wall 显示所有的警告消息
-Wall 选项可以打开所有类型的语法警告,以便于确定程序源代码是否是正确的,并且尽 可能实现可移植性。
对 Linux 程序开发人员来讲,GCC 给出的警告信息是很有价值的,它们不仅可以帮助程 序员写出更加健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用 GCC 编译源代 码时始终带上-Wall 选项,养成良好的习惯。
3.代码优化选项
代码优化指的是编译器通过分析源代码找出其中尚未达到最优的部分,然后对其重新进 行组合,进而改善代码的执行性能。GCC 通过提供编译选项-On 来控制优化代码的生成,对 于大型程序来说,使用代码优化选项可以大幅度提高代码的运行速度。
• -O 选项:编译时使用选项-O 可以告诉 GCC 同时减小代码的长度和执行时间,其效 果等价于-O1。
• -O2 选项:选项-O2 告诉 GCC 除了完成所有-O1 级别的优化之外,同时还要进行一些 额外的调整工作,如处理器指令调度等。
4.调试分析选项
• -g 选项:生成调试信息,GNU 调试器可利用该信息。GCC 编译器使用该选项进行编 译时,将调试信息加入到目标文件当中,这样 gdb 调试器就可以根据这些调试信息来跟踪程 序的执行状态。
• -pg 选项:编译完成之后,额外产生一个性能分析所需的信息。
注意 需要注意的是,使用调试选项都会使最终生成的二进制文件的大小急剧增加,同时增加程序在 执行时的开销,因此调试选项通常推荐仅在程序的开发和调试阶段中使用。
下面举一个简单的例子来说明 GCC 的编译过程。首先用 vi 编辑器来编辑一个简单的 c 程序 test.c,程序清单如下。
#include <stdio.h>
int main()
{
printf("Hello,this is a test!\n");
return 0;
}
根据前面讲到的内容,使用 gcc 命令来编译该程序。
[root@localhost]# gcc –o test test.c [root@localhost]#./test
Hello,this is a test!
tyw藏书
可以从上面的编译过程看到,编译一个这样的程序非常简单,一条指令即可完成,事实 上,这一条指令掩盖了很多细节。我们可以从编译器的角度来看上述的编译过程,这对于更 好理解 GCC 编译工作原理有很好的帮助。
GCC 编译器首先做的工作是预处理:调用-E 参数可以让 GCC 在预处理结束后停止编译 过程。
# gcc –E test.c -o test.i
编译器在这一步调用 cpp 工具来对源程序进行预处理,此时会生成 test.i 文件,下面部分 列出了 test.i 文件中的内容。
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "test.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 314 "/usr/include/features.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 315 "/usr/include/features.h" 2 3 4
# 337 "/usr/include/features.h" 3 4
# 1 "/usr/include/gnu/stubs.h" 1 3 4
# 338 "/usr/include/features.h" 2 3 4
# 29 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/lib/gcc/i386-redhat-linux/3.4.4/include/stddef.h" 1 3 4
# 213 "/usr/lib/gcc/i386-redhat-linux/3.4.4/include/stddef.h" 3 4 typedef unsigned int size_t;
# 35 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/bits/types.h" 1 3 4
# 28 "/usr/include/bits/types.h" 3 4
# 1 "/usr/include/bits/wordsize.h" 1 3 4
# 29 "/usr/include/bits/types.h" 2 3 4
# 1 "/usr/lib/gcc/i386-redhat-linux/3.4.4/include/stddef.h" 1 3 4
# 32 "/usr/include/bits/types.h" 2 3 4 typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
tyw藏书
typedef unsigned long int __u_long;
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef signed short int __int16_t;
typedef unsigned short int __uint16_t;
typedef signed int __int32_t;
typedef unsigned int __uint32_t;
__extension__ typedef signed long long int __int64_t;
__extension__ typedef unsigned long long int __uint64_t;
查看代码会发现 stdio.h 的内容都被加入到该文件里去了,而且被预处理的宏定义也都作 了相应的处理。
下一步是将 test.i 编译为目标代码,这可以通过使用-c 参数来完成。
#gcc –c test.i –o test.o
GCC 默认将.i 文件看成是预处理后的 C 语言源代码,因此上述命令将自动跳过预处理步 骤而开始执行编译过程,也可以使用-x 参数让 GCC 从指定的步骤开始编译。
编译的最后一步是将上一步所生成的目标文件链接成最终的可执行文件。
# gcc test.o –o test