加载时动态链接与运行时动态链接的区别

30

将程序加载到内存中时,静态链接和动态链接有什么区别?

注:load-time dynamic linking(静态链接)和run-time dynamic linking(动态链接)为同一概念,建议根据实际情况选用其中一个术语。

5个回答

26

在加载时链接是指当可执行文件(或另一个库)引用库中的符号时,由操作系统在将可执行文件/库加载到内存时处理这些符号。

运行时链接是指使用操作系统提供的API或通过库来加载DLL或DSO,并在需要时进行符号解析的一种方式。

我对Linux DSO比Windows DLL更熟悉,但原则应该相同。.NET库可能有所不同。

在Linux中,插件架构就是这样完成的。您的程序将使用运行时链接来加载库并调用一些函数。然后可能会卸载它。它还允许加载多个导出相同符号的库而不会产生冲突。我认为DLL也会以类似的方式工作。

可执行文件的符号表中存在“空白空间”,需要由某个库填充。这些空白空间通常在加载时或编译时填充。您可以通过使用运行时链接来消除符号表中“空白空间”的需求。

另一个运行时链接有用的场景是用于调试库,或在运行时从多个ABI/API兼容库中进行选择。我经常有一个名为“foo”的库和一个名为“foo_unstable”的库,并且有一个测试应用程序可以在两者之间切换并进行一些测试。

在Linux下,要查看可执行文件在加载时链接到哪些库,可以运行ldd命令,并得到以下输出(在/bin/ls上):

linux-vdso.so.1 =>  (0x00007fff139ff000)
librt.so.1 => /lib64/librt.so.1 (0x0000003c4f200000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x0000003c4fa00000)
libcap.so.2 => /lib64/libcap.so.2 (0x0000003c53a00000)
libacl.so.1 => /lib64/libacl.so.1 (0x0000003c58e0000
操作系统将在加载时尝试加载库文件(.so文件)。可能已经将库文件加载到内存中。

17

Aiden Bell已经讲解了基础知识,我来补充一下:

在程序运行时进行的动态链接通常通过将应用程序静态链接到.lib.a文件中实现自动建立与.dll.so文件中符号的运行时链接代码。这通常是为了固定功能(例如C运行时库等),使您的程序能够获得库中的错误修复所带来的好处,同时保持可执行文件的大小较小(通过将公共代码因子化为单个库)。

而运行时链接则用于更加动态的功能,例如插件加载。如Aiden所说,您可以在运行时使用LoadLibrary()或其等效方法主动将模块附加到程序上,也许通过查询包含插件DLL的目录加载每个插件并使用自己编写的插件API与其通信。通过这样做,您的程序可以加载在应用程序编译/链接时根本不存在的模块,因此可以在部署后有机地增长。

从根本上说,这两种方法最终都会调用LoadLibrary() API,但前者使用一组固定的符号和库,而后者使用更加动态的符号和库。


2
+1 对于开发/成长的好处。模块化架构非常酷。 - Aiden Bell
2
针对 .Net 可执行文件的附加信息:它们使用运行时动态链接。如果您在“Dependency Walker”中打开 .Net DLL,您会发现它们只是在加载时与 MSCOREE.DLL 进行动态链接。更多相关信息请参见此处:https://dev59.com/eGkw5IYBdhLWcg3w8fBJ 如果您引用但不使用某个 DLL,而该 DLL 缺失,则您的应用程序将不会出错。您可以在 Debug>Windows>Modules 中查看当前已加载的 DLL。 - Curtis Yallop
1
FYI:在我所知道的所有Unix系统上,您不需要将应用程序链接到“.a”(或“.lib”)以“建立与“.so”中符号的运行时链接”。在这些平台上,您可以直接链接到“.so”。实际上,如果存在这样的变体,链接到“.a”通常会链接到静态库变体(例如,在基于Debian的发行版上的Boost库),而这通常是不需要的。Windows是一个例外,在这种情况下是必需的(并且MinGW声称不需要每次都使用它)。 - Giel

16
自从有人提出这个问题以来已经过了很长时间。Aiden和Drew的答案已经涵盖了大部分的要点。我只想从程序员的角度补充一些东西。
如果使用Load-Time动态链接,我们必须链接到LIB文件。然后在代码中,我们可以像通常一样显式调用该方法。(请参见Using Load-Time Dynamic Linking获取代码示例)
如果使用Run-Time动态链接,您必须自己管理DLL的加载/释放和函数查找。(请参见Using Run-Time Dynamic Linking获取代码示例)
关于选择两种选项之间的选择,请查看Determining Which Linking Method to Use
因此,我认为加载时动态链接只是节省程序员工作的另一种方式。但这是以一些可扩展性为代价的。只能使用与所使用的LIB文件对应的DLL作为导入库。
从根本上说,这两种链接方法都在Windows平台上使用LoadLibrary() API。

2

加载时间过早优化掉了GetProcAddress(),通过从DLL的开头创建固定偏移量来解决。旧的可执行文件不能与新的DLL一起使用,违反了SOLID原则中的开放原则;新的可执行文件不能与旧的DLL一起使用,因为函数偏移可能不同,所以违反了SOLID原则中的封闭原则。当你违反SOLID原则时,会遇到DLL地狱。

运行时不能过早优化掉GetProcAddress()调用。旧的可执行文件可以与新的DLL一起使用,但不能使用新的函数,遵循SOLID原则中的封闭原则;新的可执行文件可以与旧的DLL一起使用,但不能使用新的函数,遵循SOLID原则中的封闭原则。使用旧的可执行文件与旧的DLL,以及使用新的可执行文件与新的DLL之间的比较是遵守SOLID原则中的开放原则。

热代码重载面向对象编程。您未能遵守Liskov替换原则,其中新的DLL不能与旧的可执行文件一起使用,或者旧的DLL不能与新的可执行文件一起使用。新版本是旧版本的继承,无论它们是可执行文件还是DLL。


1

在加载时动态链接中,可执行文件链接到 DLL 库,而在运行时动态链接中,没有任何可执行文件链接到 DLL。

当应用程序的启动性能很重要时,运行时动态链接是首选。


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