对于应用程序开发人员来说,共享(.so)和静态(.a)库之间的区别完全不同于你如何使用它们 - 大致说明你需要的库代码是否被复制到你的程序中,或者只是从您的程序然后在运行时加载。
概念上(和天真地)似乎只有一种类型的库。静态与动态链接是您在构建自己的应用程序时选择的选项。 .so和.a之间的技术差异是什么,需要在构建库时进行此选择,而不是在构建应用程序时?
类比:在餐馆,您可以订购食物或者去食物,但这是您选择如何使用"食物;厨师给你做同样的汉堡包。
答案 0 :(得分:4)
所以我看到很多答案都在谈论你为什么要使用共享库而不是静态库,但我认为你的问题是为什么它们现在甚至是不同的东西,也就是说为什么不能使用共享库作为静态库,并在构建时从中提取所需内容?
以下是一些原因。其中一些是历史性的 - 请记住,在计算机系统中,二进制格式等基本内容的变化非常缓慢。
代码可以编译为依赖于它所处的地址(位置相关)或独立(位置无关)。这会影响诸如全局常量的加载,函数调用等等。如果位置相关的代码没有加载到它期望的地址,那么位置相关的代码需要修复,即加载器必须遍历代码并实际更改偏移。
对于可执行文件,这不是问题。可执行文件是第一个加载到地址空间的东西,所以它总是被加载到同一个地址。您通常不需要任何修正。但共享库由不同的可执行文件使用,由不同的进程使用。多个库可能会发生冲突:如果它们希望处于重叠的地址范围,则必须让步。如果它,并且它是位置相关的,它需要由加载器修复。但是现在您在库代码中进行了特定于流程的更改,这意味着代码不能再与其他流程共享(在运行时)。您失去了共享库的一大好处。
如果共享库使用与位置无关的代码(PIC),则它不需要修复。因此PIC适用于共享库。另一方面,PIC在某些体系结构上较慢(尤其是x86,但不是x64),因此将可执行文件编译为PIC会浪费资源。
因此,可执行文件通常被编译为位置相关的代码,而共享库被编译为与位置无关的代码。如果您使用共享库作为直接拉入可执行文件的代码的源代码,那么您将获得PIC。如果你想要PDC,你需要一个单独的代码库,这是一个静态库。当然,在大多数现代架构中,PIC的效率不如PDC,地址空间随机化等安全技术使得将可执行文件编译为PIC也很有用,所以这更多的是历史原因而非当前之一。
但是,分离静态和共享库的另一个更新的原因,以及链接时优化。
基本上,优化器对程序的信息越多,它就能越好地推理它。经典优化器基于每个模块工作:编译.c文件,优化它,生成目标代码。链接器获取所有目标文件并将它们合并在一起。这意味着优化器一次只能推理一个模块。它无法查看模块外部的被调用函数,以便对它们进行推理,甚至只是内联它们。
但是,在现代工具链中,编译器的工作方式通常不同。它不是编译和优化模块然后生成目标代码,而是采用模块,生成中间形式,可能稍微优化一下,然后将中间形式放入目标文件中。链接器实际上合并中间表示,然后在合并的表单上调用优化器和代码生成器,而不是仅仅合并目标文件和解析引用。有了更多可用信息,优化器可以做得更好。
此中间表示比原始代码更详细,更忠实于机器代码。您希望在编译过程中使用它。您不希望将其发送给客户,因为它更大,如果您使用闭源模型,也因为它更容易进行逆向工程。而且,运输它没有意义,因为加载器并不理解它,并且你不想在启动时重新优化和重新编译你的程序(除了JIT语言)。
因此,共享库包含真实对象代码。另一方面,静态库是中间代码的良好容器,因为它由链接器使用。这是静态库和共享库之间的关键区别。
最后,我们还有另一个半历史原因:联系。
Linkage定义符号(变量或函数名称)在代码单元外部的可见性。 C语言定义了两个链接:内部(在编译单元外部不可见,即static
)和外部(整个程序可见,即extern
)。你通常有很多外部可见的符号。
共享库在加载时解析了它们的符号,这应该很快。较少的符号意味着在符号表中查找更快。当然,当计算机速度较慢时,这更为相关,但它仍然可以产生明显的效果。它还会影响库的 size 。
因此,操作系统使用的目标文件规范(ELF for * nix,PE / COFF for Windows)为共享库定义了不同的可见性。您可以选择明确指定可见函数,而不是使C中的所有外部可见。 (在Windows中,只有注释为__declspec(dllexport)
或在.def文件中列出的内容从DLL导出。在Linux中,默认情况下会导出所有extern,但您可以使用__attribute__((visibility("hidden")))
不执行此操作,或者您可以指定-fvisibility=hidden
命令行开关或可见性编译指示来覆盖默认值。)
最终结果是共享库抛弃了除导出函数之外的所有符号信息。
静态库无需丢弃任何符号信息。更重要的是,你不想这样做,因为仔细指定哪些功能被导出,哪些不是一些工作,你不想做那项工作除非必要。如果您正在使用静态库,则没有必要。
因此,可交付的共享库应该最小化其导出的符号,以便快速而小巧。这使得它作为静态链接的代码存储库不太有用,您可能希望链接的函数选择更多,特别是在接口函数被内联之后(参见上面的链接时优化)。
答案 1 :(得分:3)
这是特定于操作系统的。
在Linux上,共享库具有静态库没有的一些功能
共享库是ELF共享对象文件。
共享库的linking部分在运行时发生(ld-linux.so
)
使用相同 API将共享库更新为更新版本(针对错误修复)对应用程序来说非常简单和透明(在升级共享库之后,只需重启使用它的应用程序。)
visibility
属性来限制已定义名称对该共享库的可见性dlopen
静态库变得几乎无用(至少在原理上)。在实践中,他们通常需要构建您不想依赖外部资源(例如/bin/sash
或libc.so
)或何时需要的少数可执行文件(例如ld-linux.so
)避免使用dependency hell。
开发人员还应该注意不加载相同的共享库-i.e. mmap
- ed - 两次(但dlopen
或ld-linux.so
通常非常关心这一点)。当发生这种情况时,数据段可能会重复并发生混乱。
提出问题的更好方法可能是"何时应该避免使用共享库?#34;?答案几乎是“从来没有”#34; (除了少数例外)。
阅读Program Library HowTo,C++ dlopen mini HowTo,Drepper's paper: How To Write Shared Libraries
顺便说一句,这主要是一件历史文物。在旧时代 - 1990年至1995年 - Linux 1(或Linux 0.99)内核中,内核尚未支持ELF,a.out
共享库非常痛苦(当时没有PIC,而你必须决定全局关于使用的地址段。此外,当时的处理器比今天慢了数百倍,所以运行时连接启动时间可能会有所作为。
答案 2 :(得分:2)
IMO的主要区别在于动态库可以由已构建的应用程序加载。这意味着如果dll中存在错误,则可以通过仅重建lib来修复错误(只要您不弄乱符号)。 此外,运行时链接允许dll成为扩展应用程序功能的插件。应用程序将在目录中搜索它们并加载所有这些,只要它们的接口相同。
答案 3 :(得分:1)
它与食物完全不相似,因为共享库是,共享。你几乎没有订购一个汉堡包,任何特定数量的人也会和你同时吃东西。这就是为什么你需要不同的库,他们必须驻留在内存中,其他人可以访问它们,或加载供你专用。
答案 4 :(得分:1)
静态库是对象模块的集合。 任何模块子集都可以链接到生成的应用程序中(如果所有依赖项都已解析)。与静态不同,共享库作为一个实体加载到应用程序的空间中。
共享库由操作系统加载,因此它们必须具有系统实用程序/内核识别的特殊格式。静态库由链接器/库管理器应用程序处理。虽然有一些规范,但可以开发自己的格式静态库(以及能够处理它的工具)。
答案 5 :(得分:-1)
在构建库时(或者更准确地说,在安装库时,或者在构建二进制包时),可以选择构建静态库,共享库或两者。如果依赖项只存在于一个表单中,那么选择是仅构建(和安装)该表单,还是重建/重新安装所需格式的依赖项。应用程序的构建器/安装程序面临相同的选择。影响安装选择的库之间唯一的技术差异是已安装的依赖项的状态。或者,换句话说,没有技术差异影响决定构建的决定。 (除了磁盘空间,运行时延迟等问题,但这些决定将推迟到应用程序构建之前。)
换句话说,构建应用程序时会选择 。