静态库是真正意义上的库,因为它们包含了一系列目标文件。每个目标文件通常都是从单个源文件创建而来,它包含了机器代码以及有关代码所需数据的信息。在链接步骤中,链接器会选择必要的目标文件并将它们组合成一个可执行文件。
机器代码的一个重要部分是跳转、调用和数据指针必须包含实际的内存地址。然而,如果一个目标文件需要调用另一个目标文件中的函数,则只能使用符号来引用该函数。当链接器将目标文件组合成可执行代码时,符号引用将被解析并转换为实际的内存地址。
动态库是可执行代码,可以加载到内存中并立即执行。在某些操作系统上,可能需要进行额外的步骤,通过将可执行代码移动到另一个位置来重新定位代码,这需要将代码中的所有绝对地址向左或右移动一定数量的字节。这个操作仍然比链接器组合目标文件和解析符号要快得多。
总之:
如果你曾经尝试链接一个相当大的项目,你会注意到这需要相当长的时间,可能比你愿意等待启动应用程序的时间更长。这就解释了为什么不能执行静态库。而动态库已经被优化和削减,不包含除了可执行代码之外的任何内容,因此它们不适合用作静态库。
read_png.o
write_png.o
read_jpg.o
write_jpg.o
resize_image.o
handle_error.o
read_png
和write_png
,那么其他代码片段不会被加载到可执行文件中(除了read_png
和write_png
调用的handle_error
)。链接器不知道如何找到外部对象,例如printf
。
这样会很慢。动态库优化了快速加载。
静态库没有命名空间的概念。我无法定义自己的handle_error
,因为它会与库的定义冲突。
_start
。动态库被优化,使整个库可以直接映射到内存中。printf
,除了静态库的要求之外,还有一些额外的要求:
你必须指定哪个库有printf
。
你必须以特殊的方式调用函数,让链接器插入printf
的地址。在静态库中,链接器可以修改你的代码并直接插入地址,但这在共享库中不可能。
我们不能链接动态库的一部分。即使我们的可执行文件从未调用read_jpg
,它也会被包含,因为动态库是全有或全无的。
即使它很小,函数调用的额外开销是浪费的。
Source ==compile==> Object ==link==> Executable / Shared Library
静态库是一个未进行链接的对象集合,它们被存档在一起。还有很多工作要做。
共享库则是已经链接完成并可直接加载到内存中的最终产品。
静态库先于共享库被发明出来。如果两者同时被发明,它们可能会更加相似。
ld.so
,在OS X上是dyld
,它负责在运行时解析符号。(不,我并不是想说“加载时间”。) - Dietrich Epp理论上,您可以将静态库(.a
文件)用作动态库,在程序启动时或甚至在运行时动态加载和链接它们。但是,它们并没有经过特殊编译和准备以在任意地址上运行(甚至对齐也不一定正确!),因此加载器代码必须分配内存,从.a
文件中读取必要的对象到这个内存中,并进行重大修改。当然,所有这些内存都无法共享。因此,这可能是一个非常糟糕的想法...
静态库只是一个1包含`.o或.obj`文件的tarball。当可执行文件被链接时,引用的模块(以及它们引用的模块,然后是它们引用的模块,等等)从静态库中复制出来,并附加到主程序的末尾。整个过程作为单个OS“对象”分页加载到内存中。
动态库只是将静态库的所有元素链接在一起(解决内部关系),然后将整个内容映射到内存中。(按需分页可能会使部分内存存在。)在启动动态程序时,需要进行一定程度的调整,以将主程序(其将在自己的模块中“静态”链接)与共享系统范围内的库连接起来。有时,这种调整会被推迟到每个链接元素直到进行给定的调用。在非常广泛、过于概念化的概述中,可以将静态链接归类为急切加载,而将动态链接归类为惰性加载。
静态库有优点和缺点。
✚ 没有DLL地狱(也称为依赖地狱)
✚ 对于小型运行时进程混合,内存占用更小
− 对于大型运行时进程混合的不同程序,内存占用更大
− 除非它们正在运行相同的程序,否则无法在进程之间共享任何库代码内存
− 大量程序集(如Linux / Windows / Mac足迹)占用大量空间,因为printf等在每个映像中重复出现
− 很难甚至不可能修复源自库的安全漏洞
− 单独更新库很困难,甚至不可能
✚ 单独更新库并且不破坏程序很困难,甚至不可能
1. 实际上,它们不是tar(1)格式,但与之相关。