我理解编译过程的方式:
1)预处理:将所有宏替换为实际值,删除所有注释等。将#include语句替换为您包含的文件的文字文本。
2)编译:这里不会向下钻取太深,但结果是你所在的任何体系结构的汇编文件。
3)汇编:获取汇编文件并将其转换为二进制指令,即机器代码。
4)链接:这是我感到困惑的地方。此时你有一个可执行文件。但是,如果您实际运行该可执行文件会发生什问题是你可能包含* .h文件,那些只包含函数原型?因此,如果您实际调用这些文件中的一个函数,它将没有定义,您的程序将崩溃?
如果是这样的话,在引擎盖下链接做什么到底是什么?它如何找到与您包含的.h相关联的.c文件,以及它如何将其注入到您的机器代码中?是否必须再次为该文件完成整个编译过程?
现在,我已经明白有两种类型的链接,动态和静态。当你为你创建的每个可执行文件重新编译库的源代码时是静态的吗?我不太明白动态链接是如何工作的。那么您编译一个可由您使用它的所有进程共享的可执行库?这怎么可能呢?它不会在尝试访问它的进程的地址空间之外吗?另外,对于动态链接,您是否仍需要在某个时刻编译库?是不是只是坐在那里等待使用?什么时候编译?
你能否仔细阅读上述所有误解,错误的假设并代替你的正确解释?
答案 0 :(得分:12)
此时你有一个可执行文件。
没有。此时,您有目标文件,它们本身不可执行。
但是如果你真的运行那个可执行文件会发生什么?
这样的事情:
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文件,这将不起作用:
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
[...]以及如何将其注入您的机器代码?
对象文件包含它们调用的函数的存根引用(通常以函数入口点地址的形式或显式的,可读的名称)。然后,链接器查看每个库和目标文件,找到引用(如果找不到函数定义则抛出错误),然后用实际的“调用此函数”机器代码指令替换存根引用。 (是的,这在很大程度上已经简化了,但是如果没有您询问特定的体系结构和特定的编译器/链接器,很难准确地说出来......)
当你为你创建的每个可执行文件重新编译库的源代码时是静态的吗?
没有。静态链接意味着库的目标文件的机器代码实际上被复制/合并到最终的可执行文件中。动态链接意味着将库加载到内存中一次,然后在启动可执行文件时由操作系统解析上述存根函数引用。库中的机器代码不会复制到最终的可执行文件中。 (所以在这里,工具链中的链接器只完成部分工作。)
以下内容可以帮助您实现启发:如果您静态链接可执行文件,它将是自包含的。它将在任何地方运行(无论如何都在兼容的架构上)。如果您动态链接它,它只会在计算机上运行,如果该特定计算机安装了程序引用的所有库。
所以你编译一个可由你使用它的所有进程共享的可执行库?这怎么可能呢?它不会在尝试访问它的进程的地址空间之外吗?
操作系统的动态链接器/加载程序组件负责所有这些。
另外,对于动态链接,您是否仍需要在某个时间点编译库?
正如我已经提到的:是的,它已经编译完了。然后它会在某个时刻(通常是在它第一次使用时)加载到内存中。
什么时候编译?
可以使用之前的某个时间。通常,编译库,然后安装到系统上的某个位置,以便操作系统和编译器/链接器知道它的存在,然后您可以开始编译(嗯,链接)使用该库的程序图书馆。不早。