将程序加载到内存中时,静态链接和动态链接有什么区别?
注:load-time dynamic linking(静态链接)和run-time dynamic linking(动态链接)为同一概念,建议根据实际情况选用其中一个术语。
将程序加载到内存中时,静态链接和动态链接有什么区别?
注:load-time dynamic linking(静态链接)和run-time dynamic linking(动态链接)为同一概念,建议根据实际情况选用其中一个术语。
在加载时链接是指当可执行文件(或另一个库)引用库中的符号时,由操作系统在将可执行文件/库加载到内存时处理这些符号。
运行时链接是指使用操作系统提供的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文件)。可能已经将库文件加载到内存中。Aiden Bell已经讲解了基础知识,我来补充一下:
在程序运行时进行的动态链接通常通过将应用程序静态链接到.lib
或.a
文件中实现自动建立与.dll
或.so
文件中符号的运行时链接代码。这通常是为了固定功能(例如C运行时库等),使您的程序能够获得库中的错误修复所带来的好处,同时保持可执行文件的大小较小(通过将公共代码因子化为单个库)。
而运行时链接则用于更加动态的功能,例如插件加载。如Aiden所说,您可以在运行时使用LoadLibrary()
或其等效方法主动将模块附加到程序上,也许通过查询包含插件DLL的目录加载每个插件并使用自己编写的插件API与其通信。通过这样做,您的程序可以加载在应用程序编译/链接时根本不存在的模块,因此可以在部署后有机地增长。
从根本上说,这两种方法最终都会调用LoadLibrary()
API,但前者使用一组固定的符号和库,而后者使用更加动态的符号和库。
加载时间过早优化掉了GetProcAddress()
,通过从DLL的开头创建固定偏移量来解决。旧的可执行文件不能与新的DLL一起使用,违反了SOLID原则中的开放原则;新的可执行文件不能与旧的DLL一起使用,因为函数偏移可能不同,所以违反了SOLID原则中的封闭原则。当你违反SOLID原则时,会遇到DLL地狱。
运行时不能过早优化掉GetProcAddress()
调用。旧的可执行文件可以与新的DLL一起使用,但不能使用新的函数,遵循SOLID原则中的封闭原则;新的可执行文件可以与旧的DLL一起使用,但不能使用新的函数,遵循SOLID原则中的封闭原则。使用旧的可执行文件与旧的DLL,以及使用新的可执行文件与新的DLL之间的比较是遵守SOLID原则中的开放原则。
热代码重载
是面向对象编程
。您未能遵守Liskov替换原则
,其中新的DLL不能与旧的可执行文件一起使用,或者旧的DLL不能与新的可执行文件一起使用。新版本是旧版本的继承
,无论它们是可执行文件还是DLL。
在加载时动态链接中,可执行文件链接到 DLL 库,而在运行时动态链接中,没有任何可执行文件链接到 DLL。
当应用程序的启动性能很重要时,运行时动态链接是首选。