【问题标题】:Why can dynamic libraries link to other libraries, but static ones can't?为什么动态库可以链接到其他库,而静态库不能?
【发布时间】:2019-01-24 17:37:07
【问题描述】:

考虑以下代码结构:

main.cpp -> depends on libone.a -> depends on libtwo.a

假设在main.cpp 中仅使用来自libone.a 的函数。所以实际上写main.cpp的程序员真的只关心libone.a。此时他们甚至不知道libone.a 依赖于libtwo.a

他们尝试如下编译他们的代码并得到链接器错误:

g++ -o main main.cpp -lone

-- 错误!未定义的符号!

这成为一个问题,因为由于libone.a 依赖于libtwo.a,任何使用libone.a 的人都必须知道这种依赖关系......正如您可以想象的那样,这个问题可能会发生在比单个库更多的依赖关系时,并且可以很快就变成了连接的噩梦。


解决此问题的尝试 1:

解决此问题的第一个想法是“很简单,我将在编译 libone.a 时将 libone.alibtwo.a 链接!

事实证明它并不像我希望的那么简单...编译libone.a 时无法链接libtwo.a。静态库在编译时不会链接到任何东西,而是在将库编译为可执行文件时必须链接所有依赖项。

例如,要编译依赖于一个静态库而又依赖于另一个静态库的main.cpp,您必须链接这两个库。总是。

g++ -o main main.cpp -lone -ltwo


解决此问题的尝试 2:

另一个想法是尝试将libone 编译为链接到libtwo.a 的动态库。

奇怪的是,这刚刚奏效!编译链接libone.so后,主程序只需要关心libone.so就不需要关心libtwo.a了。

g++ -o main main.cpp -lone

成功了!


在完成这个练习之后,仍然缺少一件。我似乎无法弄清楚为什么静态库不能链接到其他库,但动态库可以。事实上,动态库libone.so 在我链接libtwo.a 之前根本不会编译。不过这很好,因为作为libone.so 的作者,我会知道它对libtwo.a 的依赖——main.cpp 的作者,但是不知道。实际上他们不应该知道。

所以回到真正的问题......为什么动态库可以像这样链接到其他库而静态库不能?这似乎是动态库相对于静态库的明显优势,但我从未在任何地方看到过它!

【问题讨论】:

  • 回复:“此时他们甚至不知道 libone.a 依赖于 libtwo.a。” ——那么他们就没有做好他们的工作。选择软件组件需要了解它们做什么以及如何做,包括它们依赖的任何其他组件。
  • @PeteBecker,当“libtwo.a”依赖于“libthree.a”时会发生什么,而“libthree.a”又依赖于“libx.a”、“liby.a”和“libz.a”......而那些依赖于其他库。要求每个其他图书馆都需要所有图书馆的知识是荒谬的。
  • 这就是为什么您需要像 CMake 这样的工具来封装这些依赖项;)(在构建库时)。
  • @MatthieuBrucher,我们有那个工具......不幸的是,我们从那里获得软件的供应商使用“旧式”cmake - pre 2.8 - 并且没有获得它的依赖管理功能...... LOL (这是一个可悲的LOL...我真的在内心深处哭泣。)
  • 我为你哭泣 :(

标签: c++ linker static-linking dynamic-linking


【解决方案1】:

静态库只是对象文件的存档,没有依赖的概念,因为它从未被链接。

共享库是链接的,解决符号,并且它们可以具有依赖关系。

【讨论】:

  • 所以在一个库依赖于其他库的情况下,应该始终首选使用动态库,而不是要求程序员链接到静态库及其依赖项?
  • @tjwrona1992 这确实是一个用例问题。我经常将我的私人项目拆分为多个链接到最终应用程序的静态库。此外,有时您不能使用动态库(取决于目标和/或库版本依赖项)。
  • 在我们的系统中,我们有一堆库,它们都依赖于其他库并且它们都是静态的。它正在成为一个完全依赖的噩梦......人们通过反复试验来链接......
  • 我也有点明白你的回答在说什么,但它并没有真正回答 why 静态库未链接而 why 动态库是。为什么需要链接动态库?
  • @tjwrona1992 将动态库视为一种特殊的程序,您无法运行但仍像操作系统一样加载它。特别是在 POSIX 系统上(例如 Linux)。正如更新后的答案所说,静态库只是未链接的目标文件的集合。
【解决方案2】:

由于您的问题涉及 gcc 和 .so/.a 文件,我假设您使用的是某种 Unix 风格,它使用 ELF 文件作为目标代码。

在完成这个练习之后,仍然缺少一件。我只是 似乎无法弄清楚静态库无法链接的任何原因 其他库,但动态库可以。

静态库没有链接,正如另一个答案中提到的那样。它们只是已编译目标文件的存档。共享库实际上是链接的,这意味着链接器实际上解析了任何导出符号可访问的所有符号。将导出的符号视为库的 API。完全链接的共享库包含每个符号的定义,或者告诉操作系统(特别是动态加载器)需要哪些其他共享库才能访问该符号所需的依赖关系信息。链接器将所有这些组合成一种特殊的文件格式,称为ELF shared object (动态库)。

事实上,动态库 libone.so 不会编译 直到我链接了 libtwo.a。不过这很好,因为作为 libone.so 的作者,我会知道它对 libtwo.a 的依赖 - 但是 main.cpp 的作者不知道。实际上他们 不应该知道。

libone.so 可能编译得很好,但由于未解析的符号,没有libtwo 将无法链接。因为链接器在链接共享库时必须解析所有可访问的符号,如果找不到任何符号,它将失败。由于libone.so 使用libtwo 中的符号,链接器需要知道libtwo.a 才能找到它们。当您将静态库链接到共享库时,通过将定义直接复制到输出共享对象文件中来解析符号,因此在这一点上,libone.so 的用户对libtwo 的使用再明智不过了,因为它的符号就在libone.so

另一个选项是将共享库链接到其他共享库。如果您将libtwo.so 链接到libone.so(注意.so 后缀),则链接器通过在输出共享对象文件中添加一个特殊部分来解析libone 所需的符号,该部分表示它在运行时需要libtwo.so .稍后,当操作系统加载libone.so 时,它知道它还需要加载libtwo.so。而且,如果您的应用程序只直接使用libone,这就是您需要在构建时告诉链接器的全部内容,因为它将链接到libone,请查看它需要libtwo,并递归解析直到一切正常。

现在,操作系统在运行时必须执行的所有加载都会导致性能成本,而且如果您不小心,多个共享对象中会存在一些全局静态变量的问题。静态链接还有一些其他潜在的性能优势,我不会在这里讨论,但可以说使用动态库的平均性能并不那么好,但对于大多数现实世界的情况来说,这种差异也可以忽略不计。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-10
    • 2011-09-05
    • 2014-01-06
    • 2020-04-11
    • 2011-03-16
    • 1970-01-01
    相关资源
    最近更新 更多