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