C+程序的编译包括三个步骤:
预处理:预处理程序采用C+源代码文件,并处理#include
S,#define
S和其他预处理指令。这个步骤的输出是一个“纯”C+文件,没有预处理指令。
编译:编译器获取预处理器的输出并从中生成一个对象文件.
链接:链接器获取编译器生成的对象文件,并生成库或可执行文件。
预处理
预处理程序处理预处理指令,就像#include
和#define
。它不知道C+的语法,这就是为什么必须谨慎使用它的原因。
它一次工作在一个C+源文件上,方法是#include
具有相应文件内容的指令(通常只是声明),替换宏(#define
),并根据以下内容选择文本的不同部分:#if
, #ifdef
和#ifndef
指令。
预处理器工作在预处理令牌流上。宏替换定义为用其他令牌替换令牌(操作符)。##
当有意义时,启用合并两个令牌)。
在所有这些之后,预处理器产生一个单一的输出,这是由上面描述的转换产生的令牌流。它还添加了一些特殊的标记,告诉编译器每一行来自哪里,这样它就可以使用这些标记来生成合理的错误消息。
在此阶段,可以通过巧妙地使用#if
和#error
指令。
编撰
编译步骤对预处理程序的每个输出执行。编译器解析纯C+源代码(现在没有任何预处理器指令)并将其转换为程序集代码。然后调用底层后端(工具链中的汇编程序),将该代码组装成生成某种格式的实际二进制文件的机器代码(ELF,COFF,a.out,.)。此对象文件包含输入中定义的符号的编译代码(二进制形式)。对象文件中的符号按名称引用。
对象文件可以引用未定义的符号。如果使用声明,而不提供声明的定义,则会出现这种情况。编译器不介意这一点,只要源代码格式良好,编译器就会很高兴地生成对象文件。
编译器通常允许您在此时停止编译。这非常有用,因为使用它,您可以分别编译每个源代码文件。它提供的优点是您不需要重新编译一切如果您只更改一个文件。
生成的对象文件可以放在称为静态库的特殊档案中,以便以后更容易重用。
在这个阶段,报告了“常规”编译器错误,比如语法错误或失败的过载解析错误。
链接
链接器是编译器生成的对象文件的最终编译输出。这个输出可以是一个共享的(或者是动态的)库(虽然名称相似,但它们与前面提到的静态库没有多少共同点),也可以是一个可执行文件。
它通过用正确的地址替换对未定义符号的引用来链接所有对象文件。这些符号中的每一个都可以在其他对象文件或库中定义。如果它们是在标准库以外的库中定义的,则需要将它们告知链接器。
在这个阶段,最常见的错误是缺少定义或重复定义。前者意味着定义不存在(即它们不被写入),或者它们所在的对象文件或库没有提供给链接器。后者是显而易见的:在两个不同的对象文件或库中定义了相同的符号。