1.GCC简介
GCC(GNU Compiler Collection)是GNU推出的功能强大、性能优越的多平台编译器,1985年由理查德·马修·斯托曼开始发展,是GNU的代表作之一,现在由自由软件基金会负责维护工作。gcc可以在多种硬体平台上编译出可执行程序,其执行效率与一般的编译器相比平均效率要高20%~30%。GCC编译器能将C、C++语言源程序、汇编程序编译、链接成可执行文件。GCC原本用C开发,后来因为LLVM、Clang的崛起,它更快地将开发语言转换为C++。许多C的爱好者在对C++一知半解的情况下主观认定C++的性能一定会输给C,但是Ian Lance Taylor给出了不同的意见,并表明C++不但性能不输给C,而且能设计出更好,更容易维护的程序。
GCC的特点
gcc是一个可移植的编译器,支持多种硬件平台。例如ARM、X86等等。
gcc不仅是个本地编译器,它还能跨平台交叉编译。所谓的本地编译器,是指编译出来的程序只能够在本地环境进行运行。而gcc编译出来的程序能够在其他平台进行运行。例如嵌入式程序可在x86上编译,然后在arm上运行。
gcc有多种语言前端,用于解析不同的语言。
gcc是按模块化设计的,可以加入新语言和新CPU架构的支持。
gcc是自由软件。任何人都可以使用或更改这个软件。
GCC介绍
2.GCC工作原理
在Linux系统中,可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。gcc则通过后缀来区别输入文件的类型,下面为gcc所遵循的部分约定规则。
后缀 | 类型 |
---|---|
.c | C语言源代码文件 |
.a | 由目标文件构成的档案库文件 |
.C|.cc|.cxx | C++源代码文件 |
.h | 程序所包含的头文件 |
.i | 预处理过的C源代码文件 |
.ii | 预处理过的C++源代码文件 |
.m | Objective-C源代码文件 |
.o | 编译后的目标文件 |
.s | 汇编语言源代码文件 |
.S | 预编译的汇编语言源代码文件 |
在大学学过编译原理就知道,一个程序从源文件变成可执行文件主要经历预处理 -> 编译 -> 链接这几个过程,如下图所示。
接下来先对这几个重要名词解释一下:
源文件(Source File):就是我们编写好的代码文件,源文件本质上就是纯文本文件,它的内部并没有特殊格式。但源文件的后缀可以表明该文件中保存的是某种语言的代码(例如.c文件中保存的是C语言代码,.cpp文件中保存的是C++语言代码),这样程序员更加容易区分,编译器也更加容易识别,它并不会导致该文件的内部格式发生改变。
编译(Compile):程序语言代码是程序员理解和识别的语言,但是机器只识别机器语言,CPU只认识执行二进制形式的指令,所以需要一个工具,将程序语言代码转换成CPU能够识别的二进制指令,这个工具是一个特殊的软件,叫做编译器(Compiler)。编译器能够识别代码中的词汇、句子以及各种特定的格式,并将他们转换成计算机能够识别的二进制形式,这个过程称为编译(Compile)。编译也可以理解为“翻译”,类似于将英文翻译成中文,它是一个复杂的过程,大致包括词法分析、语法分析、语义分析、性能优化、生成可执行文件五个步骤。
链接(Link):C语言代码经过编译以后,并没有生成最终的可执行文件,而是生成了一种叫做目标文件(Object File)的中间文件(或者说临时文件)。目标文件也是二进制形式的,它和可执行文件的格式是一样的。对于 Visual C++,目标文件的后缀是.obj;对于 GCC,目标文件的后缀是.o。目标文件经过链接(Link)以后才能变成可执行文件。既然目标文件和可执行文件的格式是一样的,为什么还要再链接一次呢,直接作为可执行文件不行吗?不行的!因为编译只是将我们自己写的代码变成了二进制形式,它还需要和系统组件(比如标准库、动态链接库等)结合起来,这些组件都是程序运行所必须的。链接(Link)其实就是一个“打包”的过程,它将所有二进制形式的目标文件和系统组件组合成一个可执行文件。完成链接的过程也需要一个特殊的软件,叫做链接器(Linker)。
gcc编译程序主要经过四个过程:
预处理(Pre-Processing):主要处理以“#”开头的命令,生成.i/.ii文件
编译 (Compiling):将.c .i等文件翻译成汇编代码,生成.s/.S文件
汇编 (Assembling):将汇编代码翻译成机器代码,生成.o文件
链接 (Linking):将生成的多个目标文件(.o文件)连接起来,生成可执行文件
以下边hello.c为例,我们来看下gcc的执行过程:
#include <stdio.h>
int main(void)
{
printf("Hello world!\n");
return 0;
}
1、预处理(生成预编译文件 ,.i文件)
预处理过程主要处理那些源代码中以#开始的预编译指令,主要处理规则如下:
①将所有的#define删除,并且展开所有的宏定义;
②处理所有条件编译指令,如#if,#ifdef等;
③处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。
④删除所有的注释//和 /**/;
⑤添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;
⑥保留所有的#pragma编译器指令,因为编译器须要使用它们;
2、编译(生成预编译文件 ,.s文件)
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。该阶段主要是对预编译后的.i文件编译,生成汇编代码的.s文件。gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。
3、汇编(生成汇编代码,.o文件)
汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。该阶段是将编译后的.s文件转化成二进制文件.o的过程,利用-c
选项就可以生成二进制.o文件。
4、链接(生成可执行文件)
链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。
假定我们有一个程序名为hello.c的C语言源代码文件,要生成一个可执行文件,最简单的办法就是∶
这时,预编译、编译连接一次完成,生成一个系统预设的名为hello.out的可执行文件。
Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。
默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要使用静态链接库可以在编译时加上-static
选项,强制使用静态链接库。由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库。
一般头文件或库文件的位置在:
/usr/include及其子目录下的include文件夹
/usr/local/include及其子目录下的include文件夹
/usr/lib
/usr/local/lib
/lib
静态库链接时搜索路径顺序:
1. ld会去找GCC命令中的参数-L
2. 再找gcc的环境变量LIBRARY_PATH
3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的
动态链接时、执行时搜索路径顺序:
1. 编译目标代码时指定的动态库搜索路径
2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径
4. 默认的动态库搜索路径/lib
5. 默认的动态库搜索路径/usr/lib
库的搜索路径遵循几个搜索原则:从左到右搜索-I -l指定的目录,如果在这些目录中找不到,那么gcc会从由环境变量指定的目录进行查找。头文件的环境变量是C_INCLUDE_PATH,库的环境变量是LIBRARY_PATH,如果还是找不到,那么会从系统指定指定的目录进行搜索。
相关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径
3.GCC参数详解
gcc指令的一般格式为:
gcc [选项] 要编译的文件 [选项] [目标文件]
其中,目标文件可缺省,gcc默认生成可执行的文件为a.out
GCC常用编译选项
1、总体编译选项
选项名称 | 作用 |
---|---|
-c | 只编译不链接,生成.o为后缀的目标文件 |
-S | 只是编译不汇编,生成汇编代码 |
-E | 只进行预编译,不做其他处理 |
-g | 在可执行程序中包含标准调试信息,使用gdb调试必需加这个选项 |
-o file | 把输出文件输出到file里,缺省为a.out |
-v | 打印出编译器内部编译各过程的命令行信息和编译器的版本 |
-I dir | 在头文件的搜索路径列表中添加dir目录 |
-L dir | 在库文件的搜索路径列表中添加dir目录 |
-static | 链接静态库 |
-llibrary | 连接名为library的库文件 |
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一起使用 |
全部参数介绍:https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html
参考: