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