# 链接器与加载器

> 计算机科学 · CIE A-Level 9618
> 来源: https://www.owlsprep.com/zh/study/cie-9618-u5-linkers-and-loaders/

本模块讲解程序执行前编译过程最后阶段中链接器和加载器的作用，你将学习静态链接、动态链接，以及常见的程序加载方法，适用于CIE A-Level计算机科学备考。

**先修:** [编译与汇编过程](https://www.owlsprep.com/zh/study/cie-9618-u5-compilation-process/); [主存组织](https://www.owlsprep.com/zh/study/cie-9618-u4-memory-organization/)

## 学习目标

- 区分链接器与加载器在程序执行中的核心作用
- 比较对比静态链接与动态链接
- 描述三种主要的程序加载类型
- 识别与链接器和加载器相关的常见错误与误区

## 1. 链接器的作用

**链接器** — 一种系统程序，它通过解析汇编器生成的多个独立编译目标文件之间的引用，将它们合并为单个可执行程序（或库）。

*例:* 使用标准数学库的C程序会包含对`sqrt()`的引用，链接器会将该引用解析为预编译的库代码。

开发大型程序时，源代码会被拆分到多个文件中以简化开发。每个文件被单独编译为目标文件，目标文件包含自身的代码和数据，以及对其他文件或外部库中定义的符号（函数、变量）的引用。

链接器主要完成三项核心任务：

1. 将输入目标文件的所有代码段和数据段合并为单个可执行镜像
2. 为每个已定义符号分配唯一的内存地址，解析符号引用
3. 重定位代码和数据，使其匹配可执行文件中的最终位置

**例题:** 一个程序有两个目标文件：`main.o`（包含调用`calculate()`的主函数）和`calc.o`（包含`calculate()`的定义）。描述链接器执行的步骤。

1. 1. 读取`main.o`和`calc.o`的符号表
2. 2. 识别`main.o`中对`calculate()`的未定义引用，并将其匹配到`calc.o`中的定义
3. 3. 将两个文件的代码段和数据段合并为单个连续的可执行镜像
4. 4. 为`calculate()`分配最终内存地址，并更新`main.o`中的引用指向该地址
5. 5. 输出完整的可执行文件，准备加载到内存中

> **考试提示:** 考试经常要求列出链接器的三项核心任务，请务必牢记

## 2. 静态链接 vs 动态链接

链接可以在编译阶段完成（静态链接），也可以在加载/运行阶段完成（动态链接）。下表对比了两种方式：

| 特性 | 静态链接 | 动态链接 |
| --- | --- | --- |
| 执行时机 | 执行前的编译阶段 | 加载阶段或运行阶段 |
| 可执行文件大小 | 更大，包含所有库代码 | 更小，仅包含引用 |
| 库更新 | 需要完整重新编译才能更新 | 无需重新编译程序即可更新库 |
| 兼容性 | 无依赖问题，独立完整 | 如果存在错误版本的库可能运行失败 |
| 内存占用 | 每个程序拥有独立的库代码副本 | 多个程序共享一份库代码副本 |

**例题:** 一名开发者要向数千用户分发一款桌面应用，对于系统共享库，他们应该使用静态链接还是动态链接？解释原因。

1. 开发者应该使用动态链接
2. 动态链接生成的可执行文件体积小得多，减小用户的下载体积
3. 它允许操作系统在多个运行的应用之间共享一份通用系统库的副本，节省主存
4. 共享库的安全更新可以由操作系统完成，无需用户下载新版本的应用

## 3. 加载器的作用

**加载器** — 将可执行程序从辅助存储加载到主存，并为CPU执行做好准备的系统程序。

*例:* 当你双击电脑上的应用时，操作系统的加载器会将应用加载到RAM中运行它。

链接器生成可执行文件后，它会被存储在辅助存储中。当程序启动时，加载器完成以下核心任务：

1. 读取可执行文件头，获取代码段、数据段和栈段的大小
2. 在主存中分配足够的空闲空间存储整个程序
3. 将可执行文件中的代码和数据复制到分配的内存中
4. 解析所有剩余的对共享库的动态链接引用
5. 设置程序的栈和堆
6. 跳转到程序的入口点开始执行

**例题:** 解释当你在电脑上启动动态链接的网页浏览器时，加载器的作用。

1. 1. 用户请求启动浏览器，因此操作系统调用加载器
2. 2. 加载器读取浏览器的可执行文件头，在主存中分配足够空间存储浏览器的代码和数据
3. 3. 加载器将浏览器的核心代码从可执行文件复制到分配的内存中
4. 4. 加载器定位浏览器使用的所有所需共享库（例如图形库、网络库）
5. 5. 加载器将缺失的库加载到内存中，解析所有对库函数的引用
6. 6. 加载器设置浏览器的栈和堆，然后跳转到浏览器的入口点开始执行

## 4. 常见的加载类型

CIE A-Level考试需要你掌握三种主要的程序加载类型：

- **绝对加载**：链接器为程序分配固定的内存地址，因此加载器将程序加载到这些预先指定的地址。实现简单，但不支持多道程序设计。
- **可重定位加载**：程序可以被加载到主存中任意空闲块，加载器会调整所有地址引用，使其匹配内存块的起始地址。支持多道程序设计。
- **动态加载**：仅当程序模块在运行时被调用时才会将其加载到内存。未使用的模块永远不会被加载，因此可以节省内存。

**例题:** 一个仅有64KB内存的嵌入式系统运行一个包含很多可选功能的大型程序（例如仅连接打印机时才会用到的打印机驱动），它应该使用哪种加载方式？为什么？

1. 该嵌入式系统应该使用动态加载
2. 动态加载仅在需要时才加载模块，因此可选的打印机驱动仅当实际连接了打印机才会被加载
3. 这为活跃的程序组件节省了宝贵的有限主存空间，避免未使用的代码浪费内存

## 常见错误

- **错误做法:** 混淆链接器和加载器的作用，认为加载器会解析静态引用
  - 原因: 静态引用在可执行文件保存前就已经解析完成，因此这项工作由链接器完成
  - 正确做法: 链接器解析静态引用并生成可执行文件；加载器将可执行文件加载到主存中。仅动态引用由加载器解析
- **错误做法:** 声称静态链接生成的可执行文件比动态链接更小
  - 原因: 静态链接会将所有用到的库代码复制到可执行文件中，因此体积更大
  - 正确做法: 动态链接生成的可执行文件更小，因为共享库代码不会被包含在可执行文件中
- **错误做法:** 将动态加载和动态链接混淆为同一个概念
  - 原因: 动态链接指的是引用解析发生的时机，而动态加载指的是模块加载到内存的时机
  - 正确做法: 动态链接是关于解析符号引用的概念；动态加载是关于模块何时加载到内存的概念，二者是相互独立的概念
- **错误做法:** 声称绝对加载支持多道程序设计
  - 原因: 绝对加载使用预先分配的固定地址，因此如果多个程序共享地址空间就无法同时运行
  - 正确做法: 可重定位加载允许程序被加载到任意空闲内存块并相应调整地址，因此支持多道程序设计

## 速查表

| 组件/类型 | 核心作用 | 关键特性 |
| --- | --- | --- |
| 链接器 | 合并目标文件，解析引用 | 输出完整的可执行文件 |
| 加载器 | 将可执行文件加载到主存 | 为CPU执行程序做好准备 |
| 静态链接 | 在编译阶段完成链接 | 独立完整，可执行文件更大 |
| 动态链接 | 在加载/运行阶段完成链接 | 共享库，可执行文件更小 |
| 绝对加载 | 加载到固定地址 | 不支持多道程序设计 |
| 可重定位加载 | 加载到任意空闲块 | 支持多道程序设计 |
| 动态加载 | 调用时才加载模块 | 为大型程序节省主存 |

## 下一步

链接器和加载器是编译过程的最后阶段，将你编写的源代码转换为电脑上运行的程序。它们是系统软件的核心组件，与操作系统配合管理程序执行和主存使用。本主题在CIE A-Level 9618第1卷中频繁考察，经常会有题目要求比较不同链接方式或描述加载器的核心功能。理解链接器和加载器的工作原理也能帮助你排查真实世界的常见错误，例如Linux或Windows系统上缺失共享库的问题。接下来，你可以基于本知识进一步学习其他核心系统软件主题、内存管理技术和操作系统进程管理。

- [系统软件的类型](https://www.owlsprep.com/zh/study/cie-9618-u5-types-of-system-software/)
- [虚拟内存](https://www.owlsprep.com/zh/study/cie-9618-u5-virtual-memory/)
- [文件管理](https://www.owlsprep.com/zh/study/cie-9618-u5-file-management/)

---

来自 [OwlsPrep](https://www.owlsprep.com) —— A-Level / IB / AP / IGCSE 免费学习指南，依据官方考纲编写。原页面：https://www.owlsprep.com/zh/study/cie-9618-u5-linkers-and-loaders/
