C/C++静态与动态库示例

C/C++ Static vs dynamic libraries example

本文关键字:动态 C++ 静态      更新时间:2023-10-16

我正在学习静态和动态库。到目前为止,我明白为什么我需要一个动态库。如果有什么变化,最好插入一个新版本,所有应用程序都会自动更新,甚至不会注意到。

a) 非常适合插件,b) 使用同一库的多个应用程序c) 当您需要更正错误时进行维护。

然而,为什么有人会使用静态库?我的意思是优势在哪里?有人举了一个例子让我更好地理解吗?这是为了使产品成为专有产品吗?

编辑:由于评论中的混乱。我知道什么是静态库,我也知道动态库之间的区别。我无法理解为什么有人会使用静态库而不仅仅是源代码本身。我想我现在开始明白静态库提供了以下优势:

a) 更好的代码维护b) 更快的编译时间

静态库和动态库之间还有另一个区别,在某些情况下可能会变得很重要,我很惊讶没有人提到这一点。

  • 当链接静态库时,符号(例如函数名)在链接(编译)时被解析,因此对库函数的调用被解析为对最终可执行文件中地址的直接调用。

  • 对于动态库,这种情况发生在运行时,当库加载到进程空间时(通常是在进程启动期间)。符号必须映射到进程的地址空间中。根据符号的数量(可能非常大)和启动时加载的库的数量,延迟可能是非常明显的。

有一本关于Linux上动态库的优秀深入指南-如何编写共享库。它对我们大多数人来说太详细了,但即使略读一下,也会给你带来许多令人惊讶的见解。例如,它说在OpenOffice的1.0版中,它在发布期间必须进行150多万次字符串比较!

一种获得这种感觉的方法是将LD_DEBUG设置为符号,将LD_DEBUG_OUTPUT设置为某个文件,运行一个程序并查看该文件以查看启动时进行的活动。

编译器可以对静态库进行各种其他优化,而对动态库则无法进行这些优化。例如,编译器可以从静态库中删除未使用的函数。它不会知道在动态库中这样做。但还有更高级的优化。编译器可以将静态库函数中的代码拉入主程序,这将消除函数调用。非常聪明的编译器可以做得更多。对于静态库来说,天空确实是极限,但动态库让这一切变得更加困难或不可能。

然而,更实际的原因可能是,静态链接是大多数库编译器的默认选项,所以很多人最终都会使用它。要创建动态库,通常必须创建一个额外的文件,公开某些函数。尽管文件往往相对简单,但如果你不花时间去做,那么你的库最终都是静态的。

正如在另一篇文章中提到的,使用静态库管理依赖关系往往更容易,因为您可以控制一切。您可能不知道用户的系统上安装了什么dll/so。

静态库基本上是对象文件的ZIP。与只分发对象文件相比,它唯一的优势是它是一个包含整个库的单个文件。用户可以使用您的头和库来构建他们的应用程序。

因为它只是对象文件的ZIP,所以编译器对对象文件所做的任何操作都可以与静态库一起使用,例如,死代码消除和整个程序优化(也称为链接时间代码生成)。与动态库不同,编译器不会在最终程序中包含共享库中未使用的部分。

对于某些构建系统,它也使连接接缝更容易。例如,对于MSVC++,我通常会有一个"生产"EXE项目,一个"测试"EXE项目并将常见的东西放在静态库中。这样,当我进行构建时,就不必重新构建所有常见的东西。

1.)共享库需要位置无关代码(库需要-fpic或-fpic),位置无关代码需要设置。这使得代码更大;例如:

*部分原因是编译器效率低下,如所述

long realfoo(long, long);
long foo(long x, long y){
return realfoo(x,y);
}
//static
foo:
jmp realfoo #
//shared (-fpic code)
foo:
pushl %ebx #
call __x86.get_pc_thunk.bx #
addl $_GLOBAL_OFFSET_TABLE_, %ebx # tmp87,
subl $16, %esp #,
pushl 28(%esp) # y
pushl 28(%esp) # x
call realfoo@PLT #
addl $24, %esp #,
popl %ebx #
ret
__x86.get_pc_thunk.bx:
movl (%esp), %ebx #,
ret
  1. 使用前面的示例,如果启用并支持适当的优化,则可以考虑在静态构建中内联realfoo()。这是因为编译器可以直接访问归档文件(libfoo.a)中的对象。您可以将.a文件视为包含单个对象文件的伪目录。

  2. "不必为库中的错误重新编译整个二进制文件"这两种方式都有好处。如果你的二进制文件没有使用有问题的bug,那么它可能是从静态二进制文件中编译出来的,用最新主干中的代码替换共享库可能会引入(多个)其他尚未报告的bug。

  3. 初始启动时间。尽管许多共享库的支持者会建议使用共享库可以减少启动时间,因为其他程序已经在使用共享库,而且(有时)二进制大小较小。在实践中,除了一些非常基本的X11应用程序之外,这种情况很少发生。有趣的是,我在X上的启动时间从5秒多降到了1/10秒左右,因为我从glibc+X11共享的股票切换到了使用musl-libc和tinyX11的静态构建。在实践中,大多数静态二进制文件最终启动得更快,因为它们不需要初始化每个依赖库中的所有(可能未使用的)符号。此外,对同一二进制文件的后续调用可以获得与共享库相同的预加载好处。

  4. 当动态库不适合静态生成时,请使用该库。例如,gnu-libc(又名glibc)在静态构建方面非常糟糕,基本的hello世界的容量接近1Mb,而musl-libc、diet-libc和ucbc都在10kb左右构建hello世界。除此之外,glibc将在静态构建中省略一些关键(主要与网络相关)功能。

  5. 如果库的关键功能依赖于"插件",则使用共享库;gtk图标加载和其他一些功能,例如,过去可以使用内置的插件进行构建,但在最长的一段时间内,这些功能都被破坏了,所以唯一的办法就是使用共享库。有些C库不支持加载任意库(例如musl),除非它们是作为共享库构建的,所以如果你需要将库作为"插件"或"模块"动态加载,你可能至少需要共享C库。静态构建的一个解决方法是使用函数指针,并为GTK的特定情况提供更好、更稳定的GUI工具包。如果您正在构建一种可扩展的编程语言,如perl或python,那么您将需要共享库功能,以便用编译语言编写优化的插件。

  6. 如果您绝对需要使用具有与静态建筑不兼容的许可证的库,请使用共享库。(没有静态链接子句的AGPL、GPL、LGPL)。。。当然,如果/当你没有源代码的时候。

  7. 当您想要更积极的优化时,请使用静态构建。许多经验丰富的库,如libX11,都是在cdecl作为主要调用约定时编写的。因此,许多X11函数按照与该函数操作的结构相同的顺序(而不仅仅是指向该结构的指针)接受大量参数。。。这在cdecl调用约定中是有意义的,因为理论上您可以只移动堆栈指针并调用函数。然而,一旦您使用一定数量的寄存器进行参数传递,这种情况就会崩溃。对于静态构建,可以通过内联和链接时间优化来减轻其中一些无意的后果。这只是一个例子。当函数边界被删除时,还会出现许多其他优化。

  8. 内存安全性可以是任意一种。使用静态二进制文件,您不易受到LD_PRELOAD攻击(静态二进制文件可能只有1个符号-入口点),但共享(以及静态饼图,如果支持的话)可以具有地址空间随机化,这仅适用于少数支持位置独立可执行文件的体系结构上的静态构建(尽管大多数常见体系结构现在都支持pie)。

  9. 共享库可以(有时)生成较小的二进制文件,因此,如果您使用的共享库无论如何都将在系统上,则可以稍微减少包大小(从而减少服务器负载)。然而,如果您无论如何都要交付共享库,那么除非进行一些疯狂的主动内联,否则库和二进制文件的组合大小将始终大于静态二进制文件。一个很好的折衷方案是使用共享的公共系统库(例如libc、X11、zlib、png、glib和gtk/qt/其他默认工具包),同时使用静态库来处理不常见的依赖项或特定于包的库。chrome浏览器在一定程度上做到了这一点。确定目标Linux发行版中共享/静态阈值的一个好方法是在默认安装中使用ldd或objdump-x迭代*/bin和*/sbin,并通过sort和uniq解析输出以确定一个好的截止值。如果你正在分发使用大多数相同库的多个二进制文件,那么一堆静态构建会增加膨胀,但你可以考虑使用多调用二进制文件(如busybox、toybox、mupdf或netpbm)

  10. 如果您想避免"DLL地狱"或跨发行版兼容性,请使用静态构建。一个发行版中构建的静态二进制文件应该适用于任何其他合理更新的发行版(主要是内核版本,以防止尝试使用不受支持的系统调用)。

有关静态链接优势的更多信息,请参阅:stali(静态linux的缩写)

关于glibc前维护者的一些共享图书馆宣传,请参阅Ulrich Drepper的"静态链接被认为是有害的"尽管他提到的静态链接问题中大约有一半是glibc特有的问题(饮食、musl和ucbc没有相同的问题)。

如果您认为静态库在实现完全优化方面还不够,请查看Sean Barrett的单文件库列表。这些函数通常可以配置为使所有函数都是静态的,这样二进制文件就可以作为单个编译单元构建。这实现了一些甚至无法通过链接时间优化实现的优化,但您最好有一个支持代码折叠的IDE。

静态库很好,那么你想要的是没有任何冲突dll问题的小程序包。此外,使用静态链接,加载和初始化库的时间大大减少。

但作为缺点,您可以注意到二进制文件的一些大小增加。

WIKI 上的静态库

如果你需要一些小程序,它们只使用一个巨大库中非常小但有时略有不同的部分(这通常发生在大型开源库中),那么最好不要构建大量的小型动态库,因为它们将变得难以管理。在这种情况下,只静态链接所需的部分可能是个好主意。

对于动态库,如果所有库都不存在,那么应用程序就不会运行。因此,如果您有一个包含库的分区,但它变得不可用,那么应用程序也不可用。如果应用程序具有静态库,那么这些库始终存在,因此没有什么可以阻止应用程序工作。这通常有助于您在维护模式下启动系统。

例如,在Solaris系统上,在某些分区可能不存在的情况下可能需要运行的命令存储在/sbin下。sbin是静态二进制文件的缩写。如果分区不可用,这些应用程序仍然可以工作。