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

时间:2019-01-21 16:20:34

标签: c++ c compiler-construction

我最近阅读了CSAPP,并对其中的编译系统部分有疑问。

现在我们有一个使用HelloWorld.c的示例(只需打印hello world)。该书说在预处理阶段,他们用此头文件的内容替换了“ #include”行。但是,当我打开stdio.h时,我发现只有printf()的声明,没有具体的实现。因此,在编译系统中,何时将引入printf()的特定实现?

书还说,在链接阶段,链接器(ld)链接了helloworld.o和printf.o。为什么链接器知道将我的目标文件链接到printf.o?在编译系统中,为什么要在第一步(预处理阶段)中声明此功能,而在最后一步(链接阶段)中链接具体的实现?

5 个答案:

答案 0 :(得分:1)

实际上,过于简化:

  • 您可以将函数编译到库中(例如,Unix上的.a.so文件)。
  • 该库具有一个函数主体(汇编指令)和一个函数名称。例如库libc.so具有printf函数,该函数从库文件0xaabbccdd中的字符编号libc.so开始。
  • 您要编译程序。
  • 您需要知道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;的程序集。
  • 链接器搜索已编译的程序,并显示call printf。然后,它说:“哦,您的代码中没有printf正文”。因此,它将在库中搜索printf,以查看其位置。链接器会遍历与您的程序链接的所有库,并在标准库中找到printf-位于地址libc.so的{​​{1}}中。因此,链接器将0xaabbccdd替换为call printf类指令。
  • 在所有“符号”(即函数名,变量名)被“解析”(链接器在某处找到它们)之后,您可以运行程序。 goto libs.so file to address 0xaabbccdd将跳至指定位置的文件call printf中。

我上面编写的内容仅用于说明目的。

答案 1 :(得分:0)

为什么链接程序知道将我的目标文件链接到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个文件。

答案 2 :(得分:0)

  

为什么链接程序知道将我的目标文件链接到printf.o

因为编译器在生成的内容(通常称为目标文件(.o))中注明了这一点。

  

为什么要在第一步中声明此功能...

要了解它。

  

...并在最后一步中链接具体的实现

因为不需要更早进行此操作。

答案 3 :(得分:0)

所有C和C ++标准都告诉您,您需要#include给定的头文件以引入一些功能(在某些平台上甚至没有必要,尽管包含进来是一个好主意,从那时起,您正在编写便携式代码)。

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

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

答案 4 :(得分:0)

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

通过包含标头,您只需在编译时(暂时)指定声明的函数的实现(在标头内部)在其他位置即可。然后在链接阶段,这些功能实现会在您的代码中“添加”。