导语
GCC
(GNU Compiler Collection
,GNU 编译器套件) 是由 GNU 开发的编程语言编译器,支持C、C++、Objective-C、Fortran、Java、Ada和Go语言等多种预言的前端,以及这些语言的库(如libstdc++、libgcj等等),它是以 GLP 许可证所发行的自由软件,也是 GNU 计划的关键部分。GCC 原本作为GNU操作系统的官方编译器,现已被大多数类 Unix 操作系统(如Linux、BSD、Mac OS X 等)采纳为标准的编译器,GCC同样适用于微软的Windows 。
本文主要记录 GCC 学习使用过程中的一些操作,便于后期使用时查阅,前期入门阶段主要参考An Introduction to GCC
。
编译C程序
单个源文件
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
编译源代码:
$ gcc helloworld.c
未指定编译输出文件名则默认输出 a.out
。
$ gcc -Wall helloworld.c -o helloworld
-o: output 该选项用于指定存储机器码的输出文件;
-Wall: Warning all 该选项用于打开所有最常用的编译警告;
$ ./helloworld
Hello, world!
多个源文件
如果将源码分为多个源文件,我们也可以使用以下命令进行编译:
$ gcc -Wall hello.c world.c hello_world.c -o helloworld
对于包含多个源文件的工程,我们可以将编译过程分为两个阶段:
第一阶段:所有
.c
.h
源文件经过编译,分别生成对应的.o
对象文件;
第二阶段:所有.o
对象文件经过链接生成最终的可执行文件。
.o
对象文件包含机器码,任何对其他文件中的函数或变量的内存地址的引用都留着没有被解析,这些都留给链接器 GNU ld
生成可执行文件时再处理。
源文件生成对象文件
$ gcc -Wall -c hello.c world.c hello_world.c
注意:这里不需要使用
-o
来指定输出文件名,-c
选项会自动生成与源文件同名的对象文件对象文件生成可执行文件
$ gcc hello.o world.o hello_world.o -o hello_world
注意:这里不需要使用者
-Wall
,因为源文件已经成功编译成对象文件了,链接是一个要么成功要么失败的过程。
通常,链接快于编译,因此,对于大型项目,将不同功能在不同的源文件中进行实现,在修改功能时,可以只编译被修改的文件,显著节省时间。
静态库文件
编译生成的 .o
对象文件可以通过归档器 GNU ar
打包成 .a
静态库文件,将某些功能提供给外部使用。在上述多个源文件的例子当中,我们可以将 hello.o
和 world.o
打包成静态库文件:
$ ar cr libhello.a hello.o world.o
这样便生成了 .a
库文件。在使用的时候,编译器需要通过路径找到对应的库文件,而标准的系统库,通常能在 /usr/lib
和 /lib
目录下找到,自己工程中生成的库文件的位置需要通过编译选项告知给编译器。
一种直接的方式是在编译命令中通过绝对路径或者相对路径指定静态库的位置,例如:
$ gcc -Wall hello_world.c ./libhello.a -o helloworld
实际上,为了避免使用长路径名,我们可以使用 -l
来链接库文件,例如:
$ gcc -Wall -L./ hello_world.c -lhello -o helloworld
两种方式的效果应当是一致的,这里 -L
选项指示库文件的位置位于 ./
,而 -lhello
选项会指示编译器试图链接 ./
目录下的文件名为 libhello.a
的静态库文件。
编译选项
库文件
外部库文件包含2种类型:静态库和共享库。
静态库文件格式为
.a
,文件包含对象文件的机器码,链接静态库文件时,链接器将程序用到的外部函数的机器码从静态库文件中提取出来,并复制到最终的可执行文件当中。
共享库文件格式为.so
,表示 shared object ,使用共享库的可执行文件仅仅包它含用到的函数的表格,而不是外部函数所在对象文件的整个机器码。共享库在使用时需要先于可执行文件加载到内存,并支持同时被多个程序共享,这个过程称为动态链接( dynamic linking )。
相比于静态库,共享库具备如下优点:
- 减少可执行程序文件大小;
- 多个程序共用;
- 库文件升级无需重新编译可执行程序
搜索路径
在编译过程中,如果找不到头文件会报错,默认情况下,GCC会在下面的目录中搜索头文件,这些路径称为 include路径
:
/usr/local/include/
/usr/include/
同理,在链接过程中,如果找不到库文件也会报错,默认情况下,GCC在下面的目录中搜索库文件,这些路径称为 链接路径
:
/usr/local/lib/
/usr/lib/
如果需要检索其他路径下的头文件或者库文件,可以通过 -I
和 -L
的方式来分别扩展头文件和库文件的搜索路径,这里介绍2种搜索路径的设置方法:
-命令行选项
$ gcc -Wall [-static] -I<INC_PATH> -L<LIB_PATH> <INPUT_FILES> -l<INPUT_LIBS> -O <OUTPUT_FILES>
在前面生成的静态库文件的基础上,我们可以进一步生成最终的可执行文件:
$ gcc -Wall [-static] -I. -L. hello_world.c -lhello -o helloworld
上述命令,-I
将指定头文件位于当前路径 .
,-L
将指定库文件位于当前路径 .
, -lhello
指定参与编译的自定义的库文件。
需要注意的是,gcc 编译器在使用 -l
选项时会默认优先链接到共享库文件,如果确认使用静态库,则可以使用 -static
选项进行限制。
-环境变量
我们还可以在 shell 登录文件(例如 .bashrc
)中,预先扩展可能用到的头文件目录和库文件目录,这样,每次登录shell时,将会自动设置他们。
对于C头文件路径,我们有环境变量 C_INCLUDE_PATH
,对于C++头文件路径,我们有环境变量 CPP_INCLUDE_PATH
。 例如:
$ C_INCLUDE_PATH=./
$ export C_INCLUDE_PATH
对于静态库文件,我们有环境变量 LIBRARY_PATH
:
$ LIBRARY_PATH=./
$ export LIBRARY_PATH
对于共享库文件,我们有环境变量 LD_LIBRARY_PATH
:
$ LD_LIBRARY_PATH=./
$ export LD_LIBRARY_PATH
上述目录经过环境变量指定后,将在标准默认目录之前搜索,后续编译过程也无需在编译命令中指定了。上面的编译指令也可以进一步简化:
$ gcc -Wall hello_world.c -lhello -o helloworld
对于多个搜索目录,我们可以遵循标准Unix搜索路径的规范,在环境变量中用冒号分割的列表进行表示:
DIR1:DIR2:DIR3: ...
DIR
可以用单个点 .
指示当前目录。举个例子:
$ C_INCLUDE