是否可以在编译时链接,并删除单独的链接步骤?
答案 0 :(得分:9)
您一次编译一个或多个翻译单元,但就语言而言,编译时每个TU都被认为是孤立的。您将一个或多个翻译单元链接在一起。
所以,如果程序中的所有TU都是同时编译的,那么你可以在那时链接它们(好吧,通常链接会在编译之后立即进行,但这是一个内部细节,没有什么可以阻止你从编写一个编译器/链接器,以某种方式交错步骤,以便在所有编译完成之后但在任何链接开始之前没有单点发生。)
但是,如果你只编译一个TU中的一个TU,这些TU稍后会链接在一起制作一个程序,那么你当然不能同时链接。链接什么?其他TU甚至可能尚未编写,特别是如果您正在编译的TU是作为静态链接库分发。
答案 1 :(得分:5)
简短回答:是的,这完全有可能。事实上,它实际上已经完成了。
一些旧的Pascal编译器(例如Turbo Pascal的早期版本)没有单独的链接器。要创建可执行文件,需要将所有代码编译在一起。他们只是将整个标准库(全部约为8千字节)复制到可执行文件中,而不是跟踪使用哪些标准库函数,只链接到所需的那些函数。
为了实现这一点,您显然需要快速编译器,小型项目或(可能)两者。
当你在使用64千字节RAM的系统工作时,大容量存储是一个大约100到200千字节左右的软盘驱动器,你没有留下很多选择。如今,我无法想象任何人会忍受相同(甚至类似)的限制。
所有这一切,它不是一个非常适合C或C ++的模型。它们是从一开始就设计的,假设有单独的编译和链接。当你至少模仿单独的链接时,恰当的语言部分(例如,文件级静态变量)才真正起作用。
答案 2 :(得分:2)
为了让您更好地理解编译和链接过程的一个小解释,以gcc为例。我希望这能让你理解为什么在编译时很难进行链接。
编译器将源代码从一种语言翻译成另一种语言。 gcc编译器将C代码转换为汇编程序。汇编程序获取汇编代码并将其转换为目标代码。虽然目标代码主要由机器代码组成,但它不能由操作系统执行。对象代码没有必要的外部函数和库的引用来正确操作。
链接器获取编译器的各种输出并将它们组合在一起以创建应用程序。
源文件由编译器单独编译。这些来源可能会引用其他地方存在的功能。编译器对这些函数留下空引用。
链接器使用系统上所有可用文件和库的编译输出来填充这些引用。一旦解析了所有空引用,链接器就会组合所有编译器输出以创建可执行文件。
答案 3 :(得分:2)
理论上,是的,这是可能的,但你可能不会看到任何实现这样做。
例如,假设我有以下代码:
#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
。
答案 4 :(得分:0)
单独编译和链接只允许编译已更改的翻译单元。
这很好,因为它可以更快地在大型项目上构建,并减少对关键项目的测试。
一般来说,编译阶段是最慢的。必须搜索文本并构建中间形式(目标文件)。
链接阶段更快,因为它在表中查找符号并执行地址和符号解析。
通过不在大型系统中每次编译每个文件,节省了时间。
此外,节省了测试时间,因为一旦翻译单元被编译和测试,它就可以保持不变。只有经过修改的翻译单元才需要重新测试。
一个例子是编码为初始化数组的数据文件。这些数据(如字体位图)不太可能发生变化。翻译单元编译一次并保存为目标文件。这将我们的构建时间从5分钟减少到1分钟。
答案 5 :(得分:0)
简短回答:不,这是不可能的。
即使您将所有代码放入单个翻译单元,您的程序使用的库也需要链接。