C语言编译过程
1. 预处理
对C源文件的伪指令(以#
开头 )和特殊符号进行处理
伪指令包括:
- 宏定义:
#define PI (3.1415)
- 条件编译:
#if (conditions)
- 头文件:
#include <filename>
特殊符号有:__func__
、__LINE__
、__FILE__
等
这里不做语法的检查,仅仅替换
2. 编译
对语法和词法进行分析,确保指令都符合语法规则后,将其翻译成等价的中间代码或汇编代码。
进行语法和语法的分析,确保指令都符合语法规则后,将其翻译成等价的中间代码或者汇编代码。
3. 优化
中间代码的优化,主要的工作时删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除等等。
偏向硬件执行的优化,考虑如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高。
4. 汇编
将汇编语言代码翻译成目标机器指令的过程,即将优化后的汇编代码翻译成一个目标文件。
这个目标文件不能执行,因为可能需要用到其他库或者其他文件的一些函数,所以需要链接
目标文件由段组成,通常一个目标文件中至少有两段:
- 代码段:主要包含程序的指令。一般是可读和可执行的,但不可写。
- 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般是可读、可写、可执行的。
5. 链接
将有关的目标文件彼此相连接,即将一个文件中引用的符号同该符号在另一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
链接分为静态和动态。静态是涉及到的函数和符号,直接从源文件中copy过来,形成最终的可执行文件。动态只是记录这些函数和符号的一些信息,在执行的时候才从内存中查找,并映射到可执行文件的进程虚拟空间中。
两种方式的对比:静态的安全,但是可执行文件占用内存空间较大;动态的灵活,占用内存小,但是性能上可能会受到一些损害。
优化选项
gcc默认提供了5级优化选项的集合:
-O0
:无优化(默认)-O
和-O1
:使用能减少目标文件大小以及执行时间并且不会使编译时间明显增加的优化,移除冗余变量和函数(静态、内联)。在编译大型程序的时候会显著增加编译时内存的使用。能得到最佳调试视图。-O2
:包含-O1
的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化。编译器不执行循环展开以及函数内联。此选项将增加编译时间和目标文件的执行性能。(默认优化级别)-Os
:专门优化目标文件大小,执行所有的不增加目标文件大小(对齐优化)的-O2
优化选项。并且执行专门减小目标文件大小的优化选项。-O3
:打开所有-O2
的优化选项并且增加 -finline-functions, -funswitch-loops, -fpredictive-commoning, -fgcse-after-reload, -ftree-vectorize优化选项。通常和-O3 -Otime
(默认)或-O3 -Ospace
配合使用。
以-O3 -Otime
为例,以增大印象大小为代价缩短执行时间,如下:1
while (expression) body;
会优化为:
1
2
3
4
5if (expression) {
do {
body;
} while (expression);
}