C语言 在编译系统中,linker(ld)如何知道将myprogram.o链接到谁?

but5z9lq  于 2022-12-26  发布在  其他
关注(0)|答案(5)|浏览(128)

我最近读了CSAPP,对它的编译系统部分有些怀疑。
现在我们有一个使用HelloWorld.c的示例(只打印hello world),书上说在Pre-processor阶段,他们用这个头文件的内容替换了“#include“行,但是当我打开stdio. h时,发现只有printf()的声明,没有具体实现,那么在编译系统中,什么时候会引入printf()的具体实现呢?
书中还提到,在链接阶段,链接器(ld)链接了helloworld.o和printf.o,为什么链接器知道把我的目标文件链接到printf.o?在编译系统中,为什么在第一步(预处理器阶段)声明这个函数,在最后一步(链接阶段)链接具体实现?

fae0ux8s

fae0ux8s1#

实际上,过于简化:

  • 您可以将函数编译到库中(例如,.a或unix上的.so文件)。
  • 该库有一个函数体(汇编指令)和一个函数名。例如,库libc.so有一个printf函数,该函数在库文件libc.so中的字符号0xaabbccdd处开始。
  • 你想编译你的程序。
  • 你需要知道printf需要什么参数。它需要int吗?它需要char *吗?它需要uint_least64_t吗?它在头文件-int printf(const char *, ...);中。头文件告诉编译器如何调用函数(函数需要什么参数以及返回什么类型)。注意每个.c文件都是单独编译的。
  • 函数声明(函数采用的参数和返回的参数)不存储在库文件中。它存储在头文件中(仅)。库具有函数名(仅printf)和编译的函数体。头文件具有int printf(const char *, ...);,但没有函数体。
  • 你编译你的程序,编译器生成代码,这样适当大小的参数就被压入堆栈,然后你的代码从堆栈中获取函数返回的变量,现在你的程序被编译成类似push pointer to "%d\n" 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,它在libc.so中,地址是0xaabbccdd。因此链接器用call printf替换goto libs.so file to address 0xaabbccdd类型的指令。
  • 在所有的“符号”(即函数名,变量名)都被“解析”(链接器已经在某个地方找到了它们)之后,你就可以运行你的程序了。call printf将跳转到libc.so文件的指定位置。

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

oaxa6hgo

oaxa6hgo2#

为什么链接器知道将我的目标文件链接到printf.o
因为编译器会在它生成的文件中记录下这一点,这些文件通常称为对象文件(.o)。
为什么它在第一步就声明了这个函数。
去了解它。
......并在最后一步中链接具体实施
因为没有必要早点这么做。

t2a7ltrp

t2a7ltrp3#

所有的C和C++标准都告诉你,你需要#include一个给定的头文件,以便引入一些功能(在一些平台上,这甚至可能是不必要的,尽管包含是一个好主意,因为你正在编写 * 可移植 * 代码)。
这为编译器提供了很大的灵活性。
链接,如果有的话,将自动完成。注意,有些函数甚至可以硬编码到编译器本身。

klh5stk1

klh5stk14#

默认情况下,库(包含printf的实现)在C程序中每次都是链接的。
通过包含头文件,你只需要在编译时(暂时)指定声明函数的实现(在头文件中)在其他地方,然后在链接阶段,这些函数实现被“添加”到你的代码中。

r7xajy2e

r7xajy2e5#

  • 为什么链接器知道把我的目标文件链接到printf.o?*

LD知道如何搜索和找到他们。你可以看到与man 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位共享对象的默认路径是/lib 64,然后是/usr/lib 64。)如果二进制文件是使用-z nodeflib链接器选项链接的,则跳过此步骤。
  • 在编译系统中,为什么在第一步(预处理器阶段)声明这个函数,而在最后一步(链接阶段)链接具体的实现?*

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

相关问题