什么是链接器?

17 Mar 2025 | 6 分钟阅读

链接器(Linker)是一个系统程序,也称为链接编辑器(link editor)和绑定器(binder),它将目标模块组合成一个单一的目标文件。通常,它是一个执行链接过程的程序;它接收由编译器生成的,一个或多个目标文件,然后将这些文件组合成一个可执行文件。模块(Modules)是指用编程语言编写的不同代码片段。链接(Linking)是一个过程,有助于将不同的代码片段收集并维护到一个可执行文件或单个文件中。借助链接器,还可以将特定模块链接到系统库中。

What is a Linker

链接器的主要功能是接收汇编器提供的对象作为输入,并为加载器生成一个可执行文件作为输出,因为它有助于将一个大问题分解成小的模块,从而简化编程任务。通常,计算机程序由各种模块组成,这些模块都是编译过的计算机程序,并且分布在独立的目标文件中。整个程序通过使用符号来引用这些不同的编译过的模块。这些独立的文件由链接器组合成一个单一的可执行文件。源代码被转换为机器代码,并且链接在编译程序的最后一步执行。

源代码 -> 编译器 -> 汇编器 -> 目标代码 -> 链接器 -> 可执行文件 -> 加载器

链接器可以从库或运行时库中收集对象。大多数链接器仅包含输出文件中被其他库或目标文件引用的文件,而不会包含整个库。库链接过程需要将其他模块链接到一些被引用的模块;因此,这可能是一个迭代过程。通常,默认情况下会链接一个或多个系统库,并且有适用于不同目的的库。

链接器还负责在程序的地址空间中排列对象。编译器通常假定一个固定的基址(例如零),因为它很少知道对象将驻留的位置。重新定位绝对跳转可能涉及加载、存储和重定位机器代码。当链接器生成的可执行输出最终加载到内存时,可能需要进行其他重定位传递。通常,在提供虚拟内存的硬件上会省略此传递。即使所有程序都加载到同一个基址,也不会发生冲突,因为每个程序都放置在其自己的地址空间中。如果可执行文件是位置无关的可执行文件(position-independent executable),则在这种文件上也会省略此传递。

在类 Unix 操作系统(如 SINTRAN III)上,链接器执行的将目标文件组装成程序的过程称为加载。此外,在某些操作系统上,程序的链接和加载都是由链接器处理的任务,称为动态链接。

链接类型

链接有两种类型,如下所示:

  1. 静态链接:静态链接是一种在编译源程序时进行的链接,在此之前,链接是在文件或对象执行之前完成的。另一方面,链接器在复制所有库例程到可执行映像时产生一个结果,这称为静态链接。与动态链接相比,它可能需要更多的内存存储和磁盘空间。但是,当它在系统上运行时,不需要库的存在,这使其更具可移植性。它生成一个完全链接的目标文件,该文件可以加载和运行,并接收一系列可重定位目标文件和命令行参数。静态链接器执行两个主要任务,如下所述:
    • 符号解析:在此过程中,每个符号都有一个预定义的任务,它将每个符号准确地与它们所属的一个符号定义关联起来。
    • 重定位:其功能是修改符号引用到重定位后的内存位置,并重定位代码和数据段。
  2. 动态链接:另一种链接类型是动态链接,它在运行时执行,在这种情况下,多个程序可以共享库的单个副本。这意味着,具有相同对象的每个模块都可以与其他模块共享对象信息,而不是将相同的对象重复链接到库中。

这些动态链接库在程序执行时加载,然后执行最终链接。此外,动态链接不需要链接器。

尽管它需要的内存空间较少,但出错和失败的可能性更大。在链接中,需要的共享库存储在虚拟内存中,这有助于节省随机访问内存。这种链接在运行时修复地址,还允许用户重新定位代码以实现代码的平稳运行。然而,并非所有代码都可以重定位。使用动态链接方法有两个好处,如下所示:

  • 在动态链接中,常用的库不需要存储在每个可执行文件中;它们只需要存储在一个位置,这有助于节省内存和磁盘空间。
  • 在库函数中,如果通过替换库来修复一个 bug,所有动态使用它的程序在重新启动后都将受益于此修正。否则,如果通过静态链接包含此函数,程序将不得不先重新链接。

动态链接也有一些缺点,如下所示:

  • 在 Windows 平台上,不兼容的更新库被称为“DLL hell”。如果新版本错误地不向后兼容,该库将基于旧版本库的性能破坏可执行文件。
  • 与库一起使用的程序可能在文档要求、正确性、性能和包方面得到认证,但如果组件可以被替换,它们可能未被认证。

重定位

由于最终输出没有对象布局的信息,编译器无法利用更短或更有效的指令。例如,跳转指令可以通过与当前位置的偏移量或绝对地址来寻址,并且根据到目标的距离,偏移量可以用不同的长度表示。这也称为自动跳转大小调整,以优化跳转。

链接器松弛(linker relaxation)传递重新分配地址,这有助于更潜在的松弛。通常,指令松弛发生在链接执行时,但在编译时,模块内部松弛可以在优化过程的一部分发生。此外,在某些情况下,松弛也可能发生在加载时。

链接器与加载器的区别

What is a Linker

在程序执行中,链接器和加载器(两个实用程序)扮演着重要角色。在执行程序之前,其代码要经过编译器、汇编器、链接器、加载器。下面是一个包含链接器和加载器之间主要区别的表格。链接器是一个系统程序,也称为链接编辑器和绑定器,它将目标模块组合成一个单一的目标文件,而加载器是一个特殊的程序,它加载由链接器生成的程序的执行模块,并为此代码准备好由计算机执行。

链接器加载器
链接器生成源程序的可执行文件。加载器的主要功能是将可执行模块加载到主内存中。
汇编器生成目标代码,链接器将其作为输入。链接器创建可执行模块,加载器将其作为输入。
为了生成可执行代码,链接器会组合所有目标模块和源代码。在主内存中,它加载可执行代码以供进一步执行。
链接编辑器和动态链接器是两种类型的链接器。绝对加载、动态运行时加载和可重定位加载是三种类型的加载器。
组合所有目标模块是链接器的另一个用途。加载器为可执行文件分配地址。
此外,在程序的地址空间中,它也负责排列对象。加载器处理程序内使用的引用。

下一主题什么是连字符