8

我理解编译过程的方式:

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

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

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

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

如果是这样的话,链接到底在什么?它如何找到与您包含的 .h 关联的 .c 文件,以及如何将其注入您的机器代码?它是否不必为该文件再次经历整个编译过程?

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

您能否通过上述内容清除所有误解,错误假设并替换您的正确解释?

4

1 回答 1

16

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

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

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

像这样的东西:

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(and file2.o) 中实现的函数的定义。但这会起作用:

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

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

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

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

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

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

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

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

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

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

什么时候编译的?

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

于 2013-10-19T09:15:52.730 回答