为什么不能静态链接动态库?

11

使用外部库时,通常需要决定使用库的静态版本还是动态版本。一般来说,它们不能互换:如果库被构建为动态库,您就不能在静态链接中使用它。

这是为什么呢?

例如:我在Windows上构建一个C++程序,并使用一个提供链接器小型 .lib 文件和运行我的可执行文件时必须存在的大型 .dll 文件的库。如果 .dll 中的库代码可以在运行时解析,为什么不能在编译时解析并直接放入我的可执行文件中?


可能是这样的。例如,异国情调的主机操作系统BS2000只有一种名为LLM(链接和加载模块)的文件格式,用于目标模块、可执行文件和共享库。 - Lorinczy Zsigmond
好的。那么有哪些格式可以用于创建单个库文件,以便同时进行静态和动态链接?为什么它不常用?在我看来,它听起来非常有用。 - pschill
这里有一个原因:在许多平台上,PIC代码比非PIC代码效率低,因此对于静态链接,您可能更喜欢使用非PIC代码,而共享对象必须是PIC(Win32是例外)。 - Lorinczy Zsigmond
1个回答

5
为什么会这样呢?大多数链接程序(AIX 链接程序是一个值得注意的例外)在链接过程中会丢弃信息。
例如,假设你有一个名为 `foo.o` 的对象文件,其中包含了 `foo` 的代码,还有一个名为 `bar.o` 的对象文件,其中包含了 `bar` 的代码。假设 `foo` 调用了 `bar`。
在将 `foo.o` 和 `bar.o` 链接成一个共享库之后,链接程序会将代码段和数据段合并,并解析引用。从 `foo` 对 `bar` 的调用变成了 `CALL $relative_offset`。此操作后,无法再确定来自 `foo.o` 和来自 `bar.o` 代码之间的边界在哪里,以及 `CALL $relative_offset` 在 `foo.o` 中使用的名称(重定位条目已被丢弃)。
现在假设你想将 `foobar.so` 与你的 `main.o` 静态链接起来,而 `main.o` 已经定义了它自己的 `bar`。
如果你有 `libfoobar.a`,那就很容易:链接程序将从存档文件中提取 `foo.o`,不会使用存档文件中的 `bar.o`,并从 `main.o` 中解析 `foo.o` 到 `bar` 的调用。
但很明显,以上任何事情都无法在 `foobar.so` 中实现——调用已经被解析为另一个 `bar`,而你无法丢弃来自 `bar.o` 的代码,因为你不知道那个代码在哪里。
在 AIX 上可能(或者至少 10 年前可能)“取消链接”共享库,并将其转换回存档文件,然后将其静态链接到其他共享库或主可执行文件中。
如果 `foo.o` 和 `bar.o` 链接到 `foobar.so` 中,调用从 `foo` 到 `bar` 是否总是被解析为 `bar.o` 中的 `bar`?这是 UNIX 共享库与 Windows DLL 工作方式非常不同的地方。在 UNIX(通常情况下),从 `foo` 到 `bar` 的调用会解析到主可执行文件中的 `bar`。
这样可以实现例如在主要的中实现和,并且所有对的调用都会一致地使用同一个堆实现。在Windows上,您需要始终跟踪“这个内存来自哪个堆实现”。UNIX模型也有缺点,因为共享库不是一个自包含的大部分隔离单元(与Windows DLL不同)。

为什么要将其解析为来自的另一个?

如果您不解析对的调用,则最终得到的程序与链接到相比完全不同。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接