是否可以在编译时链接,并删除单独的链接步骤?
6 回答
您一次编译一个或多个翻译单元,但就语言而言,编译时每个 TU 都被视为孤立的。您将一个或多个翻译单元链接在一起。
所以,如果程序中的所有 TU 同时编译,你可以在那个时候链接它们(嗯,通常链接会在编译之后立即进行,但这是一个内部细节,没有什么可以阻止你编写编译器/链接器以某种方式交错步骤,以便在所有编译完成后但在任何链接开始之前不会发生单点)。
但是,如果您只编译一个稍后将链接在一起以制作程序的 TU,那么您当然不能同时链接。与什么联系?其他 TU 甚至可能还没有编写,特别是如果您正在编译的 TU 是作为静态链接库分发的。
简短的回答:是的,这完全有可能。事实上,它实际上已经完成了。
一些旧的 Pascal 编译器(例如,Turbo Pascal 的早期版本)没有单独的链接器。为了创建您的可执行文件,您将所有代码编译在一起。他们没有跟踪使用了哪些标准库函数,也没有只链接那些需要的函数,而是简单地将整个标准库(大约 8 KB)复制到可执行文件中。
为了实现这一点,您显然需要一个快速编译器、小型项目或(可能)两者兼而有之。
当您在一个具有 64 KB RAM 的系统上工作时,大容量存储是一个可容纳大约 100 到 200 KB 左右的软盘驱动器,您没有太多选择余地。如今,我无法想象有人会忍受同样(甚至类似)的限制。
尽管如此,它并不是一个非常适合 C 或 C++ 的模型。它们的设计从一开始就假设单独编译和链接。只有当您至少模仿单独的链接时,语言本身的相当多部分(例如,文件级静态变量)才真正起作用。
为了让大家更好的理解,以gcc为例,对编译和链接过程做一个小说明。我希望这能让你理解为什么编译时链接很困难。
编译器将源代码从一种语言翻译成另一种语言。gcc 编译器将 C 代码翻译成汇编程序。汇编器获取汇编代码并将其转换为目标代码。虽然目标代码主要由机器代码组成,但它不能被操作系统执行。目标代码没有对外部函数和库的必要引用以正常运行。
链接器获取编译器的各种输出并将它们组合以创建应用程序。
源文件由编译器单独编译。这些来源可能引用了其他地方存在的函数。编译器会留下对这些函数的空引用。
链接器使用系统上可用的所有文件和库的编译输出来填充这些引用。解析完所有空引用后,链接器会组合所有编译器输出以创建可执行文件。
从理论上讲,是的,这是可能的,但您可能不会看到任何实现这样做。
例如,假设我有以下代码:
#include <stdio.h>
int main( void )
{
printf( "Hello, world\n" );
return 0;
}
编译后,我得到以下机器代码:
.file "hello.c"
.section .rodata
.LC0:
.string "Hello, world"
.text
.globl main
.type main, @function
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
movl $.LC0, %edi
call puts
movl $0, %eax
leave
ret
请注意,生成的机器代码调用库函数puts
1,但该puts
函数的机器代码不是目标文件的一部分。
这就是您需要辅助链接步骤的原因;当您编译一个翻译单元时,如果它调用另一个翻译单元或库中定义的函数,则该机器代码不会立即可供编译器使用。链接步骤对于解析对外部函数的所有引用并将这些函数的机器代码包含在最终可执行文件中是必要的。
1.如果您只传递一个参数,此版本的 gcc 将替换
printf
为。puts
单独编译和链接仅允许编译已更改的翻译单元。
这很好,因为它允许更快地构建大型项目并减少关键项目的测试。
一般来说,编译阶段是最慢的。必须搜索文本并构建中间形式(目标文件)。
链接阶段更快,因为它在表中查找符号并执行地址和符号解析。
通过不在大型系统中每次都编译每个文件,可以节省时间。
此外,还可以节省测试时间,因为一旦编译和测试了翻译单元,就可以不用管它。只有修改过的翻译单元需要重新测试。
一个示例是编码为初始化数组的数据文件。此数据(例如字体位图)不太可能更改。翻译单元编译一次并保存为目标文件。这将我们的构建时间从 5 分钟缩短到 1 分钟。
简短的回答:不,这是不可能的。
即使您将所有代码放入一个翻译单元中,您的程序使用的库也需要链接。