Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

动态链接与装载

复习

  • 静态链接:拼成自包含的可执行文件
  • 重定位:确定符号的最终地址
  • 程序在内存里的样子:代码与数据

TL;DR

  • 动态链接把库推迟到运行时才链接
  • 可执行文件里只保留对共享库的引用
  • 多个程序可以共享同一份库,节省内存
  • 代价是运行时需要找到并加载库

正文

  静态链接把所有库都“焊”进了可执行文件,代价是体积大、更新麻烦。动态链接换了个时间点:库不在生成可执行文件时链进去,而是等到程序运行时再链接。

推迟到运行时

  动态链接的程序里,并不包含库函数的全部代码,只保留一个引用。真正把库“接上”的动作,发生在程序启动(或运行时):操作系统把需要的共享库(比如 .so.dll 这样的文件)加载进内存,再把程序里的引用指向库中的函数。

  换句话说:

  • 静态链接:编译时就把库连好,运行时不操心
  • 动态链接:编译时只记“我需要它”,运行时才真正接上

省下了什么

  动态链接的好处,正好补上静态链接的短板:

  • 省空间:库在磁盘上只有一份,程序不必各带一份
  • 省内存:多个程序运行时,可以共享内存中同一份库代码
  • 更新方便:库修了 bug,换掉库文件,用它的程序就能享受修复,不必重新链接

  这些正是它被广泛采用的原因。

代价:运行时依赖

  但动态链接也带来了新的麻烦,最典型的就是依赖问题

  • 程序运行时找不到需要的库,就起不来
  • 库的版本对不上,可能出现不兼容
  • 启动时要花时间加载并解析引用

  这也是为什么我们偶尔会遇到“缺少某个动态库文件”“版本不匹配”之类的报错——它们都是“运行时才链接”这份灵活所附带的成本。

之后就是“装载”

  动态链接把“链接”推迟到了运行时,而真正把可执行文件放进内存、让它开始运行的动作,叫装载(loading)。加载共享库、把程序映射进内存、跳到入口开始执行……这些属于操作系统的职责。

  所以,编译器的故事到这里,已经不可避免地走到了操作系统的门口。下一部分会打开编译器的最后一层——语言运行时;而更完整的“程序如何被装入并运行”,早在操作系统部分已经讲过。 现在,先看看高级语言背后还需要哪些隐藏的服务。

思考题 1

  动态链接相比静态链接,省下了什么?

思考题 2

  动态链接为什么可能带来“依赖/版本”问题?

小结

知识点

  • 动态链接把库的链接推迟到运行时
  • 可执行文件只保留对共享库的引用
  • 好处是省空间、共享内存、便于更新
  • 代价是运行时依赖与版本兼容问题

参考资料

  1. Wikipedia(zh):动态链接库:运行时才链接的共享库
  2. Wikipedia(zh):载入器:把程序装入内存并准备执行

思考题答案(仅供参考)

思考题 1

  省下了磁盘与内存中的重复:库文件只存一份、多个程序运行时共享同一份库代码,不必各自携带;另外库更新也更方便,替换库文件即可让使用它的程序受益,无需重新链接。

思考题 2

  因为链接推迟到了运行时:程序启动时必须能找到所需的共享库,若找不到就无法运行;而且库的版本若与程序期望的不一致,还可能产生不兼容。这些运行时依赖正是动态链接灵活性的代价。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪