在编译系统中,链接器(ld)如何知道将myprogram.o链接到谁

In the compilation system, how does linker (ld) know who to link myprogram.o to?

本文关键字:链接 myprogram 何知道 ld 编译系统      更新时间:2023-10-16

我最近读了CSAPP,对它的编译系统部分有一些疑问。

现在我们有一个使用HelloWorld.c的示例(只打印HelloWorld)。书中说,在预处理阶段,他们用这个头文件的内容替换"#include"行。但是当我打开stdio.h时,我发现只有printf()的声明,并且没有具体的实现。那么在编译系统中,什么时候会引入printf()的具体实现呢?

书中还说,在链接阶段,链接器(ld)链接了helloworld.o和printf.o。为什么链接器知道要将我的对象文件链接到printf.o?在编译系统中,为什么它在第一步(预处理器阶段)声明这个函数,并在最后一步(链接阶段)链接具体实现?

实际上,过于简化:

  • 您可以将函数编译到库中(例如unix上的.a.so文件)
  • 该库有一个函数体(汇编指令)和一个函数名。例如,库libc.so具有从库文件libc.so中的字符号0xaabbccdd开始的printf功能
  • 你想编译你的程序
  • 您需要知道printf采用了哪些参数。需要int吗?需要char *吗?需要uint_least64_t吗?它在头文件int printf(const char *, ...);中。头告诉编译器如何调用函数(函数采用什么参数,返回什么类型)。请注意,每个.c文件都是单独编译的
  • 函数声明(函数采用的参数和返回的参数)不存储在库文件中。它存储在标头中(仅限)。该库具有函数名(仅printf)和已编译的函数体。标头具有int printf(const char *, ...);,但没有函数体
  • 你编译你的程序。编译器生成代码,以便将大小合适的参数推送到堆栈上。代码从堆栈中获取函数返回的变量。现在,您的程序已编译成类似push pointer to "%dn" on the stack; push some int on the stack; call printf; pop from the stack the returned "int"; rest of the instructions;的程序集
  • Linker搜索您编译的程序,它会看到call printf。然后它会说:"哦,你的代码中没有printf体"。然后它在库中搜索printf,看看它在哪里。链接器遍历所有链接程序的库,在标准库中找到printf——它在地址0xaabbccddlibc.so中。所以链接器用call printf代替了goto libs.so file to address 0xaabbccdd类指令
  • 在所有"符号"(即函数名、变量名)都被"解析"(链接器在某个地方找到了它们)之后,就可以运行程序了。call printf将跳转到指定位置的文件libc.so

我上面写的只是为了举例说明。

为什么链接器知道要将我的对象文件链接到printf.o

因为编译器在它生成的内容中注意到了这一点,通常称为对象文件(.o).

为什么它在第一步中声明这个函数。。。

了解它。

。。。并链接最后一步的具体实施

因为没有必要提前这样做。

所有的C和C++标准都告诉你,你需要#include一个给定的头文件来引入一些功能(在一些平台上,这甚至可能不是必要的,尽管从那时起,包含是一个好主意,因为你正在编写可移植代码)。

这为编译器提供了很大的灵活性。

链接(如果有)将自动完成。请注意,有些函数甚至可能被硬编码到编译器本身中。

默认情况下,每次在C程序中都会链接库(包含printf的实现)。

通过包含头,您只需在编译时指定(暂时)声明函数的实现(在头内)在其他地方。稍后在链接阶段,这些函数实现将被"添加"到代码中。

为什么链接器知道要将我的对象文件链接到printf.o

LD知道如何搜索和找到它们。你可以看到与男子ld.so:

如果共享对象依赖项不包含斜杠,则为按以下顺序搜索:

  • 使用二进制文件的DT_RPATH动态部分属性中指定的目录(如果存在),而DT_RUNPATH属性不存在存在不赞成使用DT_RPATH
  • 使用环境变量LD_LIBRARY_PATH,除非可执行文件在安全执行模式下运行(见下文),其中如果忽略此变量
  • 使用二进制文件的DT_RUNPATH动态节属性中指定的目录(如果存在)。仅搜索此类目录查找DT_NEEDED(直接依赖项)所需的对象项,并且不应用于这些对象的子对象,这些对象必须它们自身具有自己的DT_RUNPATH条目。这不同于DT_RPATH,其被应用于搜索依赖树中的所有子项
  • 从缓存文件/etc/ld.so.cache中,该文件包含以前在增强的库路径。但是,如果二进制文件与-z nodeflib链接链接器选项,则跳过默认路径中的共享对象。共享安装在硬件功能目录中的对象(见下文)是优先于其他共享对象
  • 在默认路径/lib中,然后是/usr/lib。(在一些64位体系结构上,64位共享对象的默认路径是/lib64,然后是/usr/lib64。)如果二进制文件与-z nodeflib链接链接器选项,则跳过此步骤

在编译系统中,为什么它在第一步(预处理器阶段)声明这个函数,并在最后一步(链接阶段)链接具体实现

在编译阶段,您需要知道要链接到什么并进行相应的编译,因此需要读取带有定义的.h文件。在链接阶段,只需要.o文件。