GNU编译器工具链
简介
GNU 编译器集合(也就是所谓的GCC)是由高级语言源码来构建二进制文件的编译器及实用程式的集合。GCC不仅是GNU/Linux上的标准编译器,而且他也是嵌入式系统研发的标准编译器。这是因为GCC支持各种不同的目标架构。例如,我们在这里所用的将专注于基于主机的研发(为我们编译所用的平台构建软件),不过如果我们是交叉编译(为一个不同的目标构建),GCC提供了40种不同的结构体系。其中包括X86,RS6000,Arm,PowerPC及其他的许多。GCC也能用在40多种不同的主机系统上(如Linux,Solaris,视窗系统)。
GCC除了标准C,还支持许多其他的程式语言。我们能使用GCC来编译C++,Ada,Java,Objective-C,FORTRAN等。
在这里我们会了解一下GCC的基本特征及一些高级的主题(包括优化)。我们也会了解一些GCC中对于程式构造有用的相关工具。
编译简介
GNU编译器在构建一个目标的过程中包含几个不同的步骤。这些步骤能分为四步:预处理,编译,汇编及链接。
预处理,编译及链接通常情况下包含在一个步骤之中,不过在这里为了演示GCC的功能,我们要进行独立的展示。
在预处理步骤,GCC将会使用所包含的头文件(.h)来对源文件(.c)进行预处理.在这一步,一些指令将会是行解释,如#ifdef,#include, #define等.处理的结果是个中间文件。通常这个文件根本不是在外问生成的,不过我们了为完整在这里进行展示。经过预处理的源文件能在编译步骤进行汇编(.s).经过汇编的文件将会在汇编阶段转换为机器指令,结果为目标文件(.o)。最后,机器码将会被链接到一起生成一个可执行的二进制文件。
编译的几个阶段如下表所示:
阶段 输入 输出 GCC例子
预处理 *.c *.i gcc -E test.c -o test.i
编译 *.i *.s gcc -S test.i -o test.s
汇编 *.s *.o gcc -c test.s -o test.o
链接 *.o * gcc test.o -o test
这些只是初步的用法。目前我们将会深入GCC来了解他的各种用法。我们首先要来了解一下演示GCC用法的各种模式,然后我们会探讨GCC最常用的一些选项。这包括调试选项,打开各种警告及优化。然后我们会了解一些和GCC相关的各种GCC工具。
GCC模式(编译,编译和链接)
作为开端的最简单的一个例子就是将一个C源文件编译成为一个可执行文件。在这个例子中,所需要的整个源码包含在一个文件中,所以我们使用下面的GCC命令:
$ gcc test.c -o test
在这里我们编译test.c文件并且生成一个名为test的可执行言论。如果在这里我们仅是希望生成这个源码的目标文件,我们能使用-c选项,如下:
$ gcc -c test.c
默认情况下将会生成名为test.o的目标文件。不过在这里我们希望生成的目标文件名为newtest.o,我们能用下面的命令:
$ gcc -c test.c -o newtest.o
我们将要研发的大多数程式都会包含不只一个文件。GCC能非常容易的在命令行处理这种情况:
$ gcc -o image first.c second.c third.c
在这里我们编译三个源文件并且将他们链接到名为image的可执行文件。
一些有用的选项
在大多数情况下,我们会把我们的头文件保存在一个和我们的源码文件目录不同的目录内。假设这样一个例子,我们的原始码保存在一个名为./src的子目录内,而我们的头文件保存在同级目录下的./inc目录中。我们能告诉GCC使用我们提供到的头文件:
$ gcc test.c -I../inc -o test
我们能使用多个-I选项来指定多个子目录:
$ gcc test.c -I../inc -I http://www.cnblogs.com/inc2 -o test
在这里我们指定另一个名为inc2的子目录。对于软件的设置我们能在编译行指定符号常量。例如,我们可在我们的头文件或是源码中定义如下的一个符号常量:
#define TEST_CONFIGURATION
我们能非常容易的使用-D选项来在命令行定义一个类似的符号常量:
$ gcc -DTEST_CONFIGURATION test.c -o test
在命令行进行指定的好处就在于我们并不必修改源文件来改动他的行为。
最后一个有用的选项就是源和汇编点缀列表。考虑下面的一个命令行:
$ gcc -c -g -Wa,-ahl,-L test.c
在这个命令行最有意思的就是-Wa选项,他会将接下来的选项传递到汇编阶段,从而使用汇编来点缀C源程式。
编译警告
在编译时如果检测到一个错误,GCC就会退出编译过程,而警告信息就是显出了我们需要修正的潜在的问题,尽管也许仍会着生一个可执行文件。GCC提供了一个丰富的警告系统,不过为了利用能检测到的警告范围,我们必须打开这些选项。
GCC最常用的用来检测警告信息就是-Wall选项。这会打开一个指定类型的所有警告信息,这由程式中最常遇见的问题组成。用法如下:
$ gcc -Wall test.c -o test
在-Wall选项中打开的警告选项如下:
选项 用途
unused-function 警告声明为静态不过却没有定义的函数
unused-label 警告声明不过却没有使用的标签
unused-parameter 警告没有使用的函数参数
unused-variable 警告没有使用的声明的局部变量
unused-value 警告计算不过却没有使用的值
format 验证printf中的格式字符串等基于指定的格式字符串的可用参数
implict-int 当声明没有指定类型时警告
implict-function- 函数的使用先于函数的声明时警告
char-subscripts 当使用字符作为数组的下标时警告
missing-braces 当初始集合没有被包含时警告
parentheses 如果()的省略引起误解时警告
return-type 警告函数的默认声明为int或是缺少返回值
sequence-point 代码元素可疑时警告
switch 在switch语句中缺少默认值,警告能在switch参数中指定的缺少情况
strict-aliasing 对于变量转换使用严格的规则
unknown-pragmas 警告#pragma指令没有被识别
uninitilized 警告使用的变量没有进行初始化(只在-O2优化级别时可用)
-Wall是-all-warnings的同义。上表列出了在-Wall允许的各种警告选项。
在这里我们要注意大多数的选项都会有一个相反的形式,所以我们能禁止他们。例如,如果我们希望打开-Wall选项,不过却要禁止没有用到的警告集合,我们能使用下面的命令:
$ gcc -Wall -Wno-unused test.c -o test
下表列出在-Wall中没有打开的一些有用的选项
选项 用途
cast-align 当一个指针进行转换并且需要的赋值增加时警告
sign-compare 当一个有符号数和一个无符号数进行比较并产生一个不正确的值时警告
missing-prototypes 如果一个全局函数没有函数原型声明而使用时警告
packed 如果一个结构提供了打包属性而没有打包事件时警告
padded 如果一个结构进填补赋值时警告
unreachable-code 如果代码不会执行警告
inline 如果一个标记为内联但却不能实现内联时警告
disabled-optimization 当优化器不能执行指定的优化时警告
最后一个有用的警告选项是-Werror。这个选项指明当检测到错误时并不简单的显示错误,编译器会将警告当作错误对待并退出编译过程。这个选项对于生成高质量的代码是相当有用的。
调试选项
如果我们希望用一个符号链接调试器来调试我们的代码,我们能指定-g选项来为GDB着生调用信息。-g选项能指定一个参数来说明生成哪种格式。如果我们希望调试信息使用dwarf-2
格式来生成调试信息,我们能使用下面的命令:
$ gcc -gdwarf-2 test.c -o test
其他的一些工具:
下面我们来看一些在我们的研发过程中有用的GNU工具。
首先,我们怎么来知道可执行程式或是目标文件的大小?size实用程式能使我们知道文本尺寸(指令条数)及data和bss段。如下面的例子:
$ size test.o
text data bss dec hex filename
789 256 4 1049 419 test.o
在这里我们列出了目标文件test.o的文件大小。我们发现文本尺寸(指令和常量)是789字节,data段是256字节,bss段(初始时自动设置为0)是4字节。如果我们要知道更为周详的内容,我们能使用objdump实用程式。我们能使用-syms参数来知道可执行程式或是目标文件的符号表,如下面的例子:
$ objdump -syms test.o
这会产生一个目标文件中可用符号列表,他们的类型(text,bss,data),长度,偏移量等内容。我们也能使用disassemble参数来反汇编程式,如下面的例子:
# objdump ?disassemble test.o
这会产生一个目标文件中的函数列表,及由GCC产生的指令。
最后,nm程式能用来理解目标文件中的符号。这个程式不仅会列每一个符号及符号类型的周详信息。更有一些其他的选项可用,我们能从nm的主页中得到更为周详的内容。