GCC简介
1、GCC经过那么多年的发展,已经从最初的C编译器转变成了编译器的集合,官方定义是GNU Complier Collection,现在的GCC不仅支持C还支持C++、Java等语言。
2、GCC是一个编译系统的驱动程序,负责解析输入的参数,依次调用预处理器(cpp)、编译器(ccl/cclplus)、汇编器(as)、链接器(ld)生成可执行文件。
3、GCC 和 G++ 的区别并不是前者用来编译C代码,后者用来编译 C++ 代码。它们的区别是GCC把后缀为c的文件当C代码处理(ccl编译),而 G++ 则当作 C++ 处理(cclplus编译),对于后缀为cpp的文件gcc和 g++ 处理过程没有什么区别。GCC默认是不能编译 C++ 程序的,需要加上-lstdc++
选项,G++ 是可以直接编译的,G++相当于是对GCC的封装。
GCC编译过程
在介绍GCC编译步骤之前,首先需要了解GCC支持的后缀文件类型。
后缀名 | 对应语言 |
---|---|
.c | C程序 |
.C/.cc/.cxx | C++程序 |
.i | 预处理后的C程序 |
.ii | 预处理后的C++程序 |
.s/.S | 汇编语言程序 |
.h | 头文件 |
.o | 目标文件 |
.a/.so | 编译后的库文件 |
GCC编译流程分为四个步骤:预处理(生成.i/.ii文件)、编译(生成.s/.S文件)、汇编(生成.o文件)、链接(生成可执行文件)。
gcc指令的一般格式为:
gcc [选项] 要编译的文件 [选项] [目标文件]
其中,目标文件可缺省,gcc默认生成可执行的文件为a.out
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
1、预处理
对于该阶段,gcc将stdio.h文件中的代码包含进这段程序,我们可以利用gcc -E test.c -o test.i
命令来生成预处理过的.i文件。-E
选项代表让gcc在预处理阶段后停止编译。test.i
文件中的内容如下所示,可以看出stdio.h
文件中的内容被展开。
extern int fprintf (FILE *__restrict __stream,
__const char *__restrict __format, ...);
......
# 8 "test.c" 2
int main()
{
printf("Hello World\n");
return 0;
}
2、编译
该阶段主要是对预编译后的.i文件编译,生成汇编代码的.s文件。gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。我们可以使用-S
选项来进行查看,该选项只进行编译而不进行汇编过程,生成汇编代码。
我们可以利用gcc -S test.i -o test.s
命令进行编译过程。test.s
文件中的内容如下所示。
.file "test.c"
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
.section .note.GNU-stack,"",@progbits
3、汇编
该阶段是将编译后的.s文件转化成二进制文件.o的过程,利用-c
选项就可以生成二进制.o文件。我们可以利用gcc -c test.s -o test.o
生成二进制代码。
4、链接
该阶段主要将成功编译的二进制文件进行链接操作,生成可执行文件。利用gcc test.o -o test
生成可执行文件test,运行./test
即可打印出hello world
。
总结一下:
gcc -E test.c -o test.i //把原代码交给cpp预处理器生成经过预处理后的中间文件test.i
gcc -S test.i -o test.s //把经过预处理之后的test.i文件交给编译器cc1生成test.s文件
gcc -c test.s -o test.o //把经过编译后汇编文件test.s交给as进行汇编,生成目标test.o文件
gcc test.o -o test //将as汇编之后的目标文件交给ld链接成一个可执行的文件test
GCC常用编译选项
1、总体编译选项
选项名称 | 作用 |
---|---|
-c | 只是编译不链接,生成目标文件.o |
-S | 只是编译不汇编,生成汇编代码 |
-E | 只进行预编译,不做其他处理 |
-g | 在可执行程序中包含标准调试信息 |
-o file | 把输出文件输出到file里 |
-v | 打印出编译器内部编译各过程的命令行信息和编译器的版本 |
-I dir | 在头文件的搜索路径列表中添加dir目录 |
-L dir | 在库文件的搜索路径列表中添加dir目录 |
-static | 链接静态库 |
-llibrary | 连接名为library的库文件 |
表中前边几个选项在GCC编译步骤中已经有所了解,主要对表中后四个选项进行介绍。
当我们进行程序开发时,基本都会用到很多函数库,从程序员的角度看,函数库就是封装的一些头文件(.h)和库文件(.a/.so),Linux系统下头文件一般默认放到/usr/include/
目录下,库文件放在/usr/lib/
目录下,如果我们使用的库不在标准路径下,我们就需要指定头文件和库文件的路径以便让gcc找到它们。
举个例子,假设上一节test.c
主要实现C语言连接mysql数据库的功能,我们从官网下载的mysql connectors的头文件在/usr/local/mysq/include/
路径下,库函数在/usr/local/mysql/lib/
路径下。
我们可以利用
gcc -c -I /usr/local/mysql/include test.c -o test.o
生成test.o
二进制文件,再利用
gcc -L /usr/local/mysql/lib -lmysqlclient test.o -o test
进行链接操作产生可执行的test
文件。这两步也可以合成一步,直接生成可执行的test
文件
gcc -I /usr/local/mysql/include -L /usr/local/mysql/lib -lmysqlclient test.c -o test
Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。
默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要使用静态链接库可以在编译时加上-static
选项,强制使用静态链接库。
比如在/usr/local/mysql/lib目录下有链接时所需要的库文件libmysqlclient.so和libmysqlclient.a,为了让GCC在链接时只用到静态链接库,可以使用下面的命令:
gcc –L /usr/local/mysql/lib -static –lmysqlclient test.o –o test
需要注意的是:
-
-I dir
和-L dir
都只是指定了路径,而没有指定文件,因此不能在路径中包含文件名。 - 另外值得详细解释一下的是
-l
选项, 它指示gcc去连接库文件libXXX.so。由于在Linux下的库文件命名时有一个规定:必须以lib三个字母开头。因此在用-l
选项指定链接的库文件名时可以省去lib三个字母。也就是说gcc在对”-lXXX”进行处理时,会自动去链接名为libXXX.so的文件。
2、其他常用编译选项
选项名称 | 作用 |
---|---|
-ansi | 支持符合ANSI标准的C程序 |
-pedantic | 允许发出ANSI C标准所列的全部警告信息 |
-pedantic-error | 允许发出ANSI C标准所列的全部错误信息 |
-Wall | 允许发出gcc提供的所有有用的报警信息 |
-Werror | 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程 |
-O | 主要进行线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化 |
-O2 | 除了完成所有“-O1”级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等 |
-O3 | 还包括循环展开和其他一些与处理器特性相关的优化工作 |
-static | 指示链接器构建一个完全链接的可执行程序,即链接静态库而不链接动态库 |
-fPIC | 指示链接器创建一个共享的目标文件,即so文件 |
-shared | 生成动态库,一般和上面的-fPIC一起使用 |