链接器

链接器的工作是将一堆目标文件(.o 文件)链接到一个二进制可执行文件中。链接过程主要涉及将符号地址解析为数字地址。链接过程的结果通常是可执行程序。

在链接过程中,链接器将获取命令行中指定的所有对象模块,在前面添加一些特定于系统的启动代码,并尝试使用其他对象文件中的外部定义来解析对象模块中的所有外部引用 (目标文件)可以直接在命令行上指定,也可以通过库隐式添加。然后,它将为目标文件分配加载地址,也就是说,它指定代码和数据最终将在完成的程序的地址空间中的位置。一旦获得了加载地址,它就可以用目标地址空间中的真实数字地址替换目标代码中的所有符号地址。该程序现在可以执行了。 ** **

这包括编译器从源代码文件创建的目标文件以及为你预编译并收集到库文件中的目标文件。这些文件的名称以 .a.so 结尾,你通常不需要了解它们,因为链接器知道大多数文件所在的位置,并根据需要自动链接它们。

隐式调用链接器

与预处理器一样,链接器是一个单独的程序,通常称为 ld(但 Linux 使用 collect2,例如)。与预处理器一样,当你使用编译器时,会自动为你调用链接器。因此,使用链接器的常规方法如下:

% gcc foo.o bar.o baz.o -o myprog

该行告诉编译器将三个目标文件(foo.obar.obaz.o)链接到一个名为 myprog 的二进制可执行文件中。现在你有了一个名为 myprog 的文件,你可以运行它,希望能做一些很酷和/或有用的东西。

显式调用链接器

可以直接调用链接器,但这很少是可取的,并且通常是特定于平台的。也就是说,在 Linux 上运行的选项不一定适用于 Solaris,AIX,macOS,Windows,对于任何其他平台也是如此。如果你使用 GCC,你可以使用 gcc -v 查看代表你执行的操作。

链接器的选项

链接器还需要一些参数来修改它的行为。以下命令将告诉 gcc 链接 foo.obar.o,但也包括 ncurses 库。

% gcc foo.o bar.o -o foo -lncurses

这实际上(或多或少)相当于

% gcc foo.o bar.o /usr/lib/libncurses.so -o foo

(虽然 libncurses.so 可能是 libncurses.a,这只是一个用 ar 创建的档案)。请注意,你应该在目标文件之后列出库(通过路径名或通过 -lname 选项)。对于静态库,它们的指定顺序很重要; 通常,对于共享库,顺序无关紧要。

请注意,在许多系统上,如果使用数学函数(来自 <math.h>),则需要指定 -lm 来加载数学库 - 但 Mac OS X 和 macOS Sierra 不需要这样。在 Linux 和其他 Unix 系统上还有其他库是独立的库,但不是在 macOS 上 - POSIX 线程,POSIX 实时和网络库就是例子。因此,链接过程因平台而异。

其他编译选项

这是你开始编译自己的 C 程序时需要知道的全部内容。通常,我们还建议你使用 -Wall 命令行选项:

% gcc -Wall -c foo.cc

-Wall 选项会使编译器警告你合法但可疑的代码构造,并且会帮助你尽早捕获大量错误。

如果你希望编译器向你发出更多警告(包括声明但未使用的变量,忘记返回一个值等),你可以使用这组选项,如 -Wall,尽管有名称,但不会全部可能的警告

% gcc -Wall -Wextra -Wfloat-equal -Wundef -Wcast-align -Wwrite-strings -Wlogical-op \
>     -Wmissing-declarations -Wredundant-decls -Wshadow …

请注意,clang 有一个选项 -Weverything ,它确实打开了 clang 中的所有警告。