【问题标题】:How exactly does linking work?链接究竟是如何工作的?
【发布时间】:2013-10-28 03:49:19
【问题描述】:

我对编译过程的理解:

1) 预处理:您的所有宏都被替换为它们的实际值,所有 cmets 都被删除,等等。用您包含的文件的文字文本替换您的#include 语句。

2) 编译:这里不会深入研究,但结果是适用于任何架构的汇编文件。

3) 汇编:获取汇编文件并将其转换为二进制指令,即机器码。

4) 链接:这是我感到困惑的地方。此时您有一个可执行文件。但是如果你真的运行那个可执行文件会发生什么?问题是您可能包含了 *.h 文件,而那些文件只包含函数原型?所以如果你真的从这些文件中调用其中一个函数,它就没有定义,你的程序会崩溃吗?

如果是这样,那么链接究竟做了什么?它如何找到与您包含的 .h 关联的 .c 文件,以及如何将其注入您的机器代码?是不是要对那个文件重新进行整个编译过程?

现在,我了解到有两种类型的链接,动态的和静态的。当您实际为您创建的每个可执行文件重新编译库的源代码时是静态的吗?我不太明白动态链接是如何工作的。所以你编译了一个可执行库,它被所有使用它的进程共享?这怎么可能呢?它不会在试图访问它的进程的地址空间之外吗?另外,对于动态链接,您是否还需要在某个时刻编译库?它只是在内存中不断地坐在那里等待使用吗?什么时候编译的?

你能把上面的所有误解和错误的假设都弄清楚,并用你的正确解释来代替吗?

【问题讨论】:

标签: c++ c assembly compilation linker


【解决方案1】:

此时你有一个可执行文件。

没有。此时,您有目标文件,它们本身不是可执行的。

但如果你真的运行那个可执行文件会发生什么?

类似这样的:

h2co3-macbook:~ h2co3$ clang -Wall -o quirk.o quirk.c -c
h2co3-macbook:~ h2co3$ chmod +x quirk.o
h2co3-macbook:~ h2co3$ ./quirk.o
-bash: ./quirk.o: Malformed Mach-o file

告诉过它不是可执行文件。

问题是你可能包含了 *.h 文件,而那些文件只包含函数原型?

其实很接近。翻译单元(.c 文件)(通常)被转换为代表其功能的汇编/机器代码。如果它调用一个函数,那么文件中会有对该函数的引用,但没有定义。

所以如果你真的从这些文件中调用其中一个函数,它就没有定义,你的程序就会崩溃?

正如我所说,它甚至不会运行。让我再说一遍:目标文件是不可执行的。

链接到底是做什么的?它如何找到与您包含的 .h 关联的 .c 文件 [...]

它没有。它查找从 .c 文件生成的其他目标文件,并最终查找库(本质上只是其他目标文件的集合)。

它会找到它们,因为你告诉它要寻找什么。假设你有一个项目,其中包含两个相互调用函数的 .c 文件,这是行不通的:

gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o
gcc -o my_prog file1.o

它将因链接器错误而失败:链接器找不到在file2.c(和file2.o)中实现的函数的定义。但这会起作用:

gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o
gcc -o my_prog file1.o file2.o

[...] 它如何将其注入您的机器代码?

对象文件包含对它们调用的函数的存根引用(通常以函数入口点地址或明确的、人类可读的名称的形式)。然后,链接器查看每个库和目标文件,找到引用(如果找不到函数定义,则抛出错误),然后用实际的“调用此函数”机器代码指令替换存根引用。 (是的,这在很大程度上被简化了,但是如果不询问特定的架构和特定的编译器/链接器,就很难说得更准确了……)

当您实际为您创建的每个可执行文件重新编译库的源代码时是静态的吗?

没有。静态链接意味着库的目标文件的机器代码实际上被复制/合并到您的最终可执行文件中。动态链接意味着一个库被加载到内存中一次,然后当你的可执行文件启动时,上述的存根函数引用由操作系统解析。库中的任何机器代码都不会被复制到您的最终可执行文件中。 (所以在这里,工具链中的链接器只完成了部分工作。)

以下内容可能会帮助您获得启发:如果您静态链接可执行文件,它将是自包含的。它可以在任何地方运行(无论如何在兼容的架构上)。如果你动态链接它,它只会在特定机器上安装了程序引用的所有库的机器上运行。

所以你编译了一个可执行库,它被所有使用它的进程共享?这怎么可能呢?它不会在试图访问它的进程的地址空间之外吗?

操作系统的动态链接器/加载器组件负责所有这些。

另外,对于动态链接,你是不是还需要在某个时刻编译库?

正如我已经提到的:是的,它已经编译好了。然后它会在某个时间点(通常是第一次使用时)加载到内存中。

什么时候编译的?

还需要一段时间才能使用。通常,编译一个库,然后将其安装到系统上的某个位置,以便操作系统和编译器/链接器知道它的存在,然后您可以开始编译(嗯,链接)使用它的程序图书馆。不是更早。

【讨论】:

  • 绝妙的答案!谢谢。当然,如果您愿意,我现在有后续问题: -- 您说如果它调用一个函数,就会有一个对函数的引用。所以目标文件实际上只包含某种可以在链接时解析的符号?这不是实际的机器指令吗?就像它只是说“printf”或类似的东西?因为它无法事先知道该函数在内存中的位置......
  • --在动态链接范例中,从您的解释看来,最终的可执行文件似乎有未解析的符号。因此,当您将程序加载到内存中时,这是否意味着操作系统在实际加载程序之前有一个中间步骤,它会查看所有未解析的符号并为它们提供这些函数的文字地址?如果你调用一个不存在的函数会发生什么?失败点在哪里?
  • @ordinary 是的,它包含存根符号(“符号”——这正是它们的名称)。这也是这些文件实际上不可执行的部分原因。
  • @ordinary 是的,动态链接的可执行文件包含未解析的符号。在 CPU 实际跳转到 main() 函数的开头之前,这些问题由操作系统(通过其动态链接器/动态加载器组件)解决。
  • @ordinary 是预编译的。它在标准库实现中,通常随操作系统或编译器一起提供。
猜你喜欢
  • 2016-08-07
  • 2014-12-16
  • 1970-01-01
  • 2015-02-19
  • 1970-01-01
  • 2011-06-26
  • 2021-08-15
  • 2012-06-08
  • 2011-10-11
相关资源
最近更新 更多