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

时间:2011-06-12 17:08:45

标签: c++ c dynamic static

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

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

但是,为什么有人会使用静态库?我的意思是什么是优势?某人有一个例子,所以我能更好地理解它吗?它是否使产品成为专有产品?

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

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

7 个答案:

答案 0 :(得分:7)

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

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

  • 使用动态库,这会在运行时中发生,此时库被加载到进程空间(通常在进程启动期间)。必须将符号映射到进程的地址空间。根据符号的数量(可能非常大)和启动时加载的库的数量,延迟可能非常明显。

Linux上有关于动态库的优秀深入指南 - How To Write Shared Libraries。这对我们大多数人来说太详细了,但即使浏览它也会给你很多惊人的见解。例如,它说在OpenOffice的1.0版本中,它必须在发布期间进行超过150万的字符串比较

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

答案 1 :(得分:5)

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

然而,更实际的原因可能是静态链接是大多数库编译器的默认设置,因此很多人最终都会使用它。要创建动态库,通常需要创建一个公开某些功能的附加文件。虽然文件往往相对简单,但如果你没有花时间去做,那么你的库最终都是静态的。

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

答案 2 :(得分:4)

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

因为它只是目标文件的ZIP,所以编译器对目标文件所做的任何事情也适用于静态库,例如,死代码消除和整个程序优化(也称为链接时代码生成)。与动态库不同,编译器不会在最终程序中包含未使用的共享库的位。

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

答案 3 :(得分:0)

静态库很好,然后你想要小包装没有任何问题与冲突的DLL。 使用静态链接时,加载和初始化库的时间也减少了很多。

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

Static librarys on WIKI

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

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

所述
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. "不必为库中的错误重新编译整个二进制文件"削减两种方式。如果您的二进制文件没有使用有问题的错误,它可能是从静态二进制文件编译而来的,用最新的主干代码替换共享库,修复程序可能会引入(多个)其他尚未报告的错误。

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

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

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

  6. 如果您绝对需要使用具有与静态构建不兼容的许可证的库,请使用共享库。 (AGPL,GPL,没有静态链接条款的LGPL)......当然,如果您没有源代码,那么。

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

  8. 内存安全可以采用任何一种方式。对于静态二进制文件,您不容易受到LD_PRELOAD攻击(静态二进制文件可能只有1个符号 - 入口点),但是共享(如果支持,则为static-pie)可以具有仅对静态可用的地址空间随机化建立在支持位置无关可执行文件的少数架构之上(尽管大多数通用架构现在都支持PIE)。

  9. 共享库可以(有时)生成较小的二进制文件,因此如果您使用的是系统上的共享库,则可以减少包大小(以及服务器负载)。但是,如果您不得不以任何方式运送共享库,那么库和二进制文件的总大小将始终大于静态二进制文件,除非有一些疯狂的攻击性内联。一个很好的折衷方案是使用共享公共系统库(例如libc,X11,zlib,png,glib和gtk / qt / other-default-toolkit),同时使用静态库来处理特定于包的非常见依赖项或库。 Chrome浏览器在某种程度上做到了这一点。确定目标Linux发行版中共享/静态阈值的一种好方法是使用ldd或objdump -x在默认安装中迭代* / bin和* / sbin,并通过sort和uniq解析输出以确定良好的截止。如果您正在分发使用大多数相同库的多个二进制文件,那么一堆静态构建将增加膨胀,但您可以考虑使用多个二进制文件(例如在busybox,toybox,mupdf或netpbm中)

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

  11. 有关静态链接优点的更多信息,请参阅:stali(静态linux的简称)

    对于来自glibc前维护者的一些共享库宣传,请参阅Ulrich Drepper的"Static Linking Considered Harmful"尽管他提到的静态链接问题大致有一半是glibc特定的问题(饮食,musl和uclibc)没有同样的问题)。

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