从C++源代码中创建可以执行的程序模块需要两步。第一步是编译器把每个.cpp文件转换为对象文件,其中包含了与源文件内容对应的机器码。第二步是链接程序把编译器生成的对象文件合并到包含完整可执行程序的文件中。
图1-5表明,3个源文件经过编译后,生成3个对应的对象文件。用于标识对象文件的文件扩展名在不同的机器环境上是不同的,这里没有显示。组成程序的源文件可以在不同的编译器运行期间单独编译,但大多数编译器都允许在一次运行期间编译它们。无论采用哪种方式,编译器都把每个源文件看作一个独立的实体,为每个.cpp文件生成一个对象文件。然后在链接步骤中,把程序的对象文件和必要的库函数组合到一个可执行文件中。

图1-5 编译和链接过程
实际上,编译是一个迭代的过程,因为在源代码中总是会有输入错误或其他错误。更正了每个源文件中的这些错误后,就可以进入链接步骤,但在这一步可能会发现有更多的错误!即使链接步骤生成了可执行模块,程序仍有可能包含逻辑错误,即程序没有生成希望的结果。为了更正这些错误,必须回过头来修改源代码,再编译。这个过程会继续下去,直到程序按照希望的那样执行为止。如果程序的执行结果不象我们宣称的那样,其他人就有可能找到我们本应发现的许多错误,这是毋庸置疑的。一般说来,如果程序非常大,就总是包含错误。
下面详细讨论一下这两个基本步骤(即编译和链接),因为在这两个步骤中有一些有趣的 东西。
1.7.1 编译
源文件的编译过程包含两个主要阶段,如图1-6所示,而它们之间的转换是自动的。第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。还有其他许多预处理指令,详见第10章。

图1-6 编译过程
这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。
在图1-6中,预处理器显示为一个独立的操作,但一般不能独立于编译器来执行这个操作。调用编译器会自动执行预处理过程,之后才编译代码。
1.7.2 链接
编译器为给定源文件输出的是机器码,执行这个过程需要较长时间。在对象文件之间并没有建立任何连接。对应于某个源文件的对象文件包含在其他源文件中定义的函数引用或其他指定项的引用,而这些函数或项仍没有被解析。同样,也没有建立同库函数的链接。实际上,这些函数的代码并不是文件的一部分。这些工作是由链接程序(有时称为链接编辑器)完成的。
如图1-7所示,链接程序把所有对象文件中的机器码组合在一起,并解析它们之间的交叉引用。它还集成了对象模块所使用的库函数的代码。这是链接程序的一种简化表示,因为这里假定在可执行模块中,模块之间的所有链接都是静态建立的。实际上有些链接是动态的,即这些链接是在程序执行时建立的。
链接程序静态地建立函数之间的链接,即在程序执行之前建立组成程序的源文件中所包含的函数链接。动态建立的函数之间的链接(在程序执行过程中建立的链接)将函数编译并链接起来,创建另一种可执行模块—— 动态链接库或共享库。动态链接库中的函数链接是在程序调用函数时才建立的,在程序调用之前,该链接是不存在的。

图1-7 链接过程
动态链接库有几个重要的优点。一个主要的优点是动态链接库中的函数可以在几个并行执行的程序之间共享,在执行的多个函数需要动态链接库中的函数所提供的服务时,这将节省同一个函数占用的内存空间。另一个优点是动态链接库在调用其中的函数之前是不会加载到内存中的。也就是说,如果不使用给定动态链接库中的函数,该动态链接库就不会占用内存空间。动态链接库是与操作系统紧密相关的一个系统功能,本书不再详述。





