GCC(gcc) 的不断发展完善使许多商业编译器都相形见绌, GCC 由 GNU 创始人 Richard Stallman首创,是 GNU 的标志产品,由于 UNIX 平台的高度可移植性, GCC 几乎在各种常见的 UNIX 平台上都有,即使是 Win32/DOS 也有 GCC 的移植。 比如说 SUN 的 Solaris 操作系统配置的编译器就是GNU 的 GCC 。
GNU 软件包括 C 编译器 GCC , C++ 编译器 G++ ,汇编器 AS ,链接器 LD ,二进制转换工具(OBJCOPY , OBJDUMP) ,调试工具 (GDB , GDBSERVER , KGDB) 和基于不同硬件平台的开发库。
在 GNU GCC 支持下用户可以使用流行的 C/C++ 语言开发应用程序,满足生成高效率运行代码、易掌握的编程语言的用户需求。
这些工具都是按 GPL 版权声明发布,任何人可以从网上获取全部的源代码,无需使用任何费用。关于 GNU 和公共许可证协议的详细资料, 读者可以参看 GNU 网站的介绍,
http://www.gnu.org/home.html。
GNU 开发工具都是采用命令行的方式,用户掌握起来相对比较困难,不如基于 Windows 系统的开发工具好用,但是 GNU 工具的复杂性是由于它更贴近编译器和操作系统的底层,并提供了更大的灵活性。一旦学习和掌握了相关工具后,就了解了系统设计的基础知识。
运行于 Linux 操作系统下的自由软件 GNU gcc 编译器,不仅可以编译 Linux 操作系统下运行的应用程序,还可以编译 Linux 内核本身,甚至可以作交叉编译,编译运行于其它 CPU 上的程序。所以,在进行嵌入式系统应用程序开发时,这些工具得到了日益广泛的应用。
GCC 是 GNU 组织的免费 C 编译器, Linux 的很多发布缺省安装的就是这种。很多流行的自由软件源代码基本都能在 GCC 编译器下编译运行。 所以掌握 GCC 编译器的使用无论是对于编译系统内核还是自己的应用程序都是大有好处的。
下面通过一个具体的例子,学习如何使用 GCC 编译器。
在 Linux 操作系统中,对一个用标准 C 语言写的源程序进行编译,要使用 GNU 的 gcc 编译器。
例如下面一个非常简单的 Hello 源程序 (hello.c):
/*******************************************************
* Institute of Automation, Chinese Academy of Sciences
* File Name : hello.c
* Description : introduce how to compile a source file with gcc
* Author : Xueyuan Nie
* Date :
*******************************************************/
void main()
{
printf("Hello the world\n") ;
}
要编译这个程序,我们只要在 Linux 的 bash 提示符下输入命令:
$ gcc -o hello hello.c
gcc 编译器就会生成一个 hello 的可执行文件。在 hello.c 的当前目录下执行 ./hello 就可以看到程序的输出结果,在屏幕上打印出 “ Hello the world ” 的字符串来。
命令行中 gcc 表示是用 gcc 来编译源程序;
-o outputfilename 选项表示要求编译器生成文件名为 outputfilename 的可执行文件,如果不指定 -o 选项,则缺省文件名是 a.out 。在这里生成指定文件名为 hello 的可执行文件,而 hello.c 是我们的源程序文件。
gcc 是一个多目标的工具。 gcc 最基本的用法是:
gcc [options] file... ,
其中的 option 是以 - 开始的各种选项, file 是相关的文件名。在使用 gcc 的时候,必须要给出必要的选项和文件名。 gcc 的整个编译过程,实质上是分四步进行的,每一步完成一个特定的工作,
这四步分别是:预处理,编译,汇编和链接。它具体完成哪一步,是由 gcc 后面的开关选项和文件类型决定的。
清楚的区别编译和连接是很重要的。编译器使用源文件编译产生某种形式的目标文件 (objectfiles) 。在这个过程中,外部的符号引用并没有被解释或替换,然后我们使用链接器来链接这些目标文件和一些标准的头文件,最后生成一个可执行文件。在这个过程中,一个目标文件中对别的文件中的符号的引用被解释,并报告不能被解释的引用,一般是以错误信息的形式报告出来。
gcc 编译器有许多选项,但对于普通用户来说只要知道其中常用的几个就够了。在这里为读者列出几个最常用的选项:
-o 选项表示要求编译器生成指定文件名的可执行文件;
-c 选项表示只要求编译器进行编译,而不要进行链接,生成以源文件的文件名命名但把其后缀由 .c 或 .cc 变成 .o 的目标文件;
-g 选项要求编译器在编译的时候提供以后对程序进行调试的信息;
-E 选项表示编译器对源文件只进行预处理就停止,而不做编译,汇编和链接;
-S 选项表示编译器只进行编译,而不做汇编和链接;
-O 选项是编译器对程序提供的编译优化选项,在编译的时候使用该选项,可以使生成的执行文件的执行效率提高;
-Wall 选项指定产生全部的警告信息。
如果你的源代码中包含有某些函数,则在编译的时候要链接确定的库,比如代码中包含了某些数学函数,在 Linux 下,为了使用数学函数,必须和数学库链接,为此要加入 -lm 选项。也许有读者会问,前面那个例子使用 printf 函数的时候为何没有链接库呢?在 gcc 中对于一些常用函数的实现, gcc 编译器会自动去链接一些常用库,这样用户就没有必要自己去指定了。有时候在编译程序的时候还要指定库的路径,这个时候要用到编译器的 -L 选项指定路径。比如说我们有一个库在/home/hoyt/mylib 下,这样我们编译的时候还要加上 -L/home/hoyt/mylib 。对于一些标准库来说,没有必要指出路径。只要它们在起缺省库的路径下就可以了, gcc 在链接的时候会自动找到那些库的。
GNU 编译器生成的目标文件缺省格式为 elf(executive linked file) 格式,这是 Linux 系统所采用的可执行链接文件的通用文件格式。 elf 格式由若干段 (section) 组成,如果没有特别指明,由标准 c 源代码生成的目标文件中包含以下段: .text( 正文段 ) 包含程序的指令代码, .data( 数据段 ) 包含固定的数据,如常量,字符串等, .bss( 未初始化数据段 ) 包含未初始化的变量和数组等。
读者若想知道更多的选项及其用法,可以查看 gcc 的帮助文档,那里有许多对其它选项的详细说明。
当改变了源文件 hello.c 后,需要重新编译它:
$ gcc -c hello.c
然后重新链接生成:
$ gcc – o hello.o
对于本例,因为只含有一个源文件,所以当改动了源码后,进行重新的编译链接的过程显得并不是太繁琐,但是,如果在一个工程中包含了若干的源码文件,而这些源码文件中的某个或某几个又被其他源码文件包含,那么,如果一个文件被改动,则包含它的那些源文件都要进行重新编译链接,工作量是可想而知的。幸运的是, GNU 提供了使这个步骤变得简单的工具,就是下面要介绍给大家的 GNU Make 工具。
GNU Make
make 是负责从项目的源代码中生成最终可执行文件和其他非源代码文件的工具。 make 命令本身可带有四种参数:标志、宏定义、描述文件名和目标文件名。
其标准形式为:
make [flags] [macro definitions] [targets]
Unix 系统下标志位 flags 选项及其含义为:
-f file 指定 file 文件为描述文件,如果 file 参数为 '-' 符,那么描述文件指向标准输入。如果没有 '-f' 参数,则系统将默认当前目录下名为 makefile 或者名为 Makefile 的文件为描述文件。在Linux 中, GNU make 工具在当前工作目录中按照 GNUmakefile 、 makefile 、 Makefile 的顺序搜索makefile 文件。
-i 忽略命令执行返回的出错信息。
-s 沉默模式,在执行之前不输出相应的命令行信息。
-r 禁止使用隐含规则。
-n 非执行模式,输出所有执行命令,但并不执行。
-t 更新目标文件。
-q make 操作将根据目标文件是否已经更新返回 "0" 或非 "0" 的状态信息。
-p 输出所有宏定义和目标文件描述。
-d Debug 模式,输出有关文件和检测时间的详细信息。
Linux 下 make 标志位的常用选项与 Unix 系统中稍有不同,下面只列出了不同部分:
-c dir 在读取 makefile 之前改变到指定的目录 dir 。
-I dir 当包含其他 makefile 文件时,利用该选项指定搜索目录。
-h help 文挡,显示所有的 make 选项。
-w 在处理 makefile 之前和之后,都显示工作目录。
通过命令行参数中的 target ,可指定 make 要编译的目标,并且允许同时定义编译多个目标,操作时按照从左向右的顺序依次编译 target 选项中指定的目标文件。如果命令行中没有指定目标,则系统默认 target 指向描述文件中第一个目标文件。
make 如何实现对源代码的操作是通过一个被称之为 makefile 的文件来完成的,在下面的小节里,主要向读者介绍一下 makefile 的相关知识。
makefile 基本结构
GNU Make 的主要工作是读一个文本文件 makefile 。 makefile 是用 bash 语言写的, bash 语言是很像 BASIC 语言的一种命令解释语言。这个文件里主要描述了有关哪些目标文件是从哪些依赖文件中产生的,是用何种命令来进行这个产生过程的。有了这些信息, make 会检查磁盘的文件,如果目标文件的日期 ( 即该文件生成或最后修改的日期 ) 至少比它的一个依赖文件日期早的话, make 就会执行相应的命令,以更新目标文件。
makefile 一般被称为 “makefile” 或 “Makefile” 。还可以在 make 的命令行中指定别的文件名。如果没有特别指定的话, make 就会寻找 “makefile” 或 “Makefile” ,所以为了简单起见,建议读者使用这两名字。如果要使用其他文件作为 makefile ,则可利用类似下面的 make 命令选项指定 makefile 文件:
$ make -f makefilename
一个 makefile 主要含有一系列的规则,如下:
目标文件名: 依赖文件名
(tab 键 ) 命令
第一行称之为规则,第二行是执行规则的命令,必须要以 tab 键开始。
下面举一个简单的 makefile 的例子。
executable : main.o io.o
gcc main.o io.o -o executable
main.o : main.c
gcc -Wall -O -g -c main.c -o main.o
io.o : io.c
gcc -Wall -O -g -c io.c -o io.o
这是一个最简单的 makefile , make 从第一条规则开始, executable 是 makefile 最终要生成的目标文件。给出的规则说明 executable 依赖于两个目标文件 main.o 和 io.o ,只要 executable 比它依赖的文件中的任何一个旧的话,下一行的命令就会被执行。但是,在检查文件 main.o 和 io.o 的日期之前,它会往下查找那些把 main.o 或 io.o 做为目标文件的规则。 make 先找到了关于 main.o 的规则,该目标文件的依赖文件是 main.c 。 makefile 后面的文件中再也找不到生成这个依赖文件的规则了。此时, make 开始检查磁盘上这个依赖文件的日期,如果这个文件的日期比 main.o 日期新的话,那么这个规则下面的命令 gcc -c main.c – o main.o 就会执行,以更新文件 main.o 。同样 make 对文件 io.o 做类似的检查,它的依赖文件是 io.c ,对 io.o 的处理和 main.o 类似。现在, 再回到第一个规则处,如果刚才两个规则中的任何一个被执行,最终的目标文件executable 都需要重建 ( 因为 executable 所依赖的其中一个 .o 文件就会比它新 ) ,因此链接命令就会被执行。
有了 makefile ,对任何一个源文件进行修改后,所有依赖于该文件的目标文件都会被重新编译 ( 因为 .o 文件依赖于 .c 文件 ) ,进而最终可执行文件会被重新链接 ( 因为它所依赖的 .o 文件被改变了 ) ,再也不用手工去一个个修改了。