1.预处理(Pre-Processing)
2.编译(Compiling)
3.汇编(Assembling)
4.链接(Linking)
1.预处理(Pre-Processing)
读取c源程序,对其中的伪指令(以#开头的指令)和 预定义符号进行处理
伪指令主要包括以下四个方面
(1)宏定义指令:
如#define,#undef。
(2)条件编译指令:
如#ifdef,#ifndef,#else,#elif,#endif,等等。
这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
(3)头文件包含指令:
如#include "FileName"或者#include <stdio.h>等。
使用< >引用的头文件是告诉编译器要到系统指定的目录下去寻找
而使用 " " 引用的头文件是告诉编译器先在默认目录下查找, 如果默认目录下找不到则再到系统目录下查找
一般情况下, 你自己写的头文件用 " ",编译器自带的头文件用< >
这样方便区别头文件的类型
如果全部用 " "也是可以的, 只不过不符合规范结构而已。
(4)预定义符号,预编译程序可以识别一些预定义符号。
C语言编译器的预定义符号:
__LINE__ 当前(源代码文件)行号 [整数]
__FILE__ 当前正在编译的文件的文件名 [字符串]
__DATE__ 当前日期,以“月月 日日 年年年年”的形式给出 [字符串]
__TIME__ 当前时间,以“HH:mm:ss”的格式给出 [字符串]
__STDC__ 如果编译器符合ANSI C标准,该宏为1,否则为0
__STDC_HOSTED__ 如果实现了所有C标准库,该宏为1,否则为0
__STDC_VERSION__ 被定义为199901L(不同编译器可能不一样,比如gcc里就没有这个预定义符号)
注:这些预定义符号的首尾为两个下划线,如果是两个单词,中间以一个下划线连接。
如果在源代码中使用了这些符号,它们会在预处理时被转换(使用gcc编译器的 -E 选项可以看到替换后的值)
C 标准里还在每个函数内预定义了一个标志符: __func__
它被定义为 static const char __func__[]="function-name";
即不能在程序内对__func__赋值,也不能改变它所指向的字符串(函数名),否则报编译错误
注:__func__是个标志符,它在预处理阶段不被替换,所以使用gcc -E 是看不到任何效果的。
预编译程序对于在源程序中出现的预定义符号将用合适的值进行替换。
预编译程序所完成的基本上是对源程序的“替代”工作。
经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。
2.编译(Compiling)
预编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
3.汇编(Assembling)
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
汇编阶段就是把编译阶段生成的".s"文件转成目标文件。
4.链接(Linking)
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);
在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。
根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
(1)静态链接
在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。
(2)动态链接
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。
经过上述4个过程,C源程序就最终被转换成可执行文件了。
gcc一些编译选项:
-std=<标准> 指定输入源文件遵循的标准
--sysroot=<目录> 将 <目录> 作为头文件和库文件的根目录
-B <目录> 将 <目录> 添加到编译器的搜索路径中
-E 仅作预处理,不进行编译、汇编和链接
-S 编译到汇编语言,不进行汇编和链接
-c 编译、汇编到目标代码,不进行链接
-o <文件> 输出到 <文件>