何时使用动态库与静态库


Answers:


299

静态库会增加二进制文件中代码的大小。它们始终会被加载,并且您使用的任何编译版本的代码都是将运行的代码版本。

动态库分别存储和版本控制。如果更新被认为与原始版本二进制兼容,则有可能加载的动态库版本不是代码随附的原始版本。

此外,动态库不一定要加载-它们通常在首次调用时就加载-可以在使用同一库的组件之间共享(多个数据加载,一个代码加载)。

大多数时候,动态库被认为是一种更好的方法,但是最初它们有一个重大缺陷(谷歌DLL地狱),但最近的Windows操作系统(尤其是Windows XP)已将其消除。


71
在Windows / Mac(没有软件包管理器)上,实际上没有充分的理由使用动态库而不是静态库。由于Windows DLL无法重定位,因此代码共享通常无法正常工作(通常每个应用程序都会交付并使用其自己的库版本)。唯一的真正好处是更新库更容易。
Zifre

5
在Mac上,我使用了很多动态库。例如,Mac OS X嵌入了sqlite3。我创建了一个具有sqlite3数据库功能的程序,用于存储性能。但是,由于很少使用动态链接,因此节省了编译时间,因此使测试更容易/更快,但是,如果我要构建发行版本,我认为我总是会使用静态库来解决兼容性问题
ReachConnection 2009年

6
@Zifre:relocatable =可以加载到其他虚拟地址。DLL当然支持这一点。
dma_k 2010年

20
@dma_k:Windows DLL可以加载到不同的地址,但这仅是因为链接程序复制了所有代码并更改了地址编号。对于共享对象,所有地址引用都是相对的,因此多个进程可以为共享对象共享相同的内存。换句话说,在Windows上,由3个程序使用的1 MB DLL = 3 MB。在Linux上,3个程序使用的MB SO = 1 MB。
Zifre 2010年

7
Windows和Linux都具有共享库的加载时重定位的概念eli.thegreenplace.net/2011/08/25/… 允许使用“位置无关代码 ”的最大功能不是Linux的特殊功能,而是增加了RIP相对寻址使用x64指令集;Windows和Linux都可以利用RIP相对寻址来减少重新定位库时的修正数量。
clemahieu 2013年

194

其他人已经充分解释了什么是静态库,但是我想指出至少在Windows上使用静态库的一些注意事项:

  • 单例:如果某些东西需要是全局/静态和唯一的,则在将其放入静态库时要非常小心。如果将多个DLL链接到该静态库,则它们将各自获得自己的单例副本。但是,如果您的应用程序是没有自定义DLL的单个EXE,则这可能不是问题。

  • 未引用的代码删除:当您链接到静态库时,只有静态库中由DLL / EXE引用的部分才会链接到您的DLL / EXE中。

    例如,如果mylib.lib包含a.obj和,b.obj并且您的DLL / EXE仅引用中的函数或变量a.objb.obj则链接器将全部丢弃。如果b.obj包含全局/静态对象,则不会执行其构造函数和析构函数。如果这些构造函数/析构函数具有副作用,那么您可能会对它们的缺失感到失望。

    同样,如果静态库包含特殊的入口点,则可能需要注意它们确实包含在内。嵌入式编程(好的,不是Windows)中的一个例子是一个被标记为位于特定地址的中断处理程序。您还需要将中断处理程序标记为入口点,以确保不会被丢弃。

    这样做的另一个结果是,静态库可能包含由于无法解析的引用而完全无法使用的目标文件,但是在您从这些目标文件中引用函数或变量之前,它不会导致链接器错误。编写库后很可能会发生这种情况。

  • 调试符号:您可能希望为每个静态库使用一个单独的PDB,或者可能希望将调试符号放置在目标文件中,以便将它们导入DLL / EXE的PDB中。Visual C ++文档介绍了必要的选项

  • RTTI:type_info如果将单个静态库链接到多个DLL,则可能会导致同一类具有多个对象。如果您的程序假定该type_info数据是“单个”数据,并使用&typeid()type_info::before(),则可能会得到不良的结果。


23
至于单例,请不要忘记DLL可能会多次加载(相同版本或多版本),并且仍然没有单例保证。
Orion Adrian

关于未引用代码删除的其他要点:对DLL的调用也需要实际调用来强制加载引用的DLL。将其添加为引用,但不包括引用该引用的任何调用,仍将获得与不调用任何内容的静态库相同的结果。唯一的区别是实际发货的东西。在这两种情况下,静态构造函数和析构函数均不会触发。
Orion Adrian

@ bk1e那不应该发生。.a将始终包含其生成时使用的所有符号。当它是静态链接到您的应用程序,是仅使用这些符号将被链接中。
万里溃败

62

库是捆绑在应用程序可执行文件中的代码单位。

dll是可执行代码的独立单元。仅当对该代码进行调用时才将其加载到进程中。一个dll可以由多个应用程序使用,并可以在多个进程中加载​​,同时在硬盘驱动器上仍然只有一个代码副本。

DLL专家:可用于在多个产品之间重用/共享代码;按需加载到过程内存中,并且在不需要时可以卸载;可以独立于程序的其余部分进行升级。

DLL缺点:DLL加载和代码变基对性能的影响;版本问题(“ dll地狱”)

易于使用的:不会对性能造成影响,因为代码始终在过程中加载并且不会重新构建;没有版本问题。

缺点:可执行文件/进程“膨胀”-所有代码都在可执行文件中,并在进程启动时加载;禁止重复使用/共享-每个产品都有自己的代码副本。


也可以在构建时使用rebase.exe或通过将/ BASE选项传递给link.exe来完成基础调整。这是否有效取决于运行时是否存在任何意外的地址空间冲突。
bk1e

24

除了静态库和动态库的技术含义(静态文件将所有内容捆绑在一个大的二进制库中,动态库将动态库捆绑在一起,允许在多个不同的可执行文件之间共享代码),还有法律含义

例如,如果您使用的是LGPL许可代码,并且与LGPL库进行静态链接(从而创建一个大的二进制文件),则您的代码将自动成为“开源”(自由自由) LGPL代码。如果链接共享对象,则只需LGPL对LGPL库本身进行的改进/错误修复。

例如,如果您要决定如何编译移动应用程序,则这将成为一个更为重要的问题(在Android中,您可以选择静态还是动态,而在iOS中,则不能选择-它始终是静态的)。


22

C ++程序分两个阶段构建

  1. 编译-生成目标代码(.obj)
  2. 链接-生成可执行代码(.exe或.dll)

静态库(.lib)只是一堆.obj文件,因此不是一个完整的程序。它尚未经历构建程序的第二个(链接)阶段。另一方面,Dll与exe类似,因此是完整的程序。

如果您构建静态库,那么它尚未链接,因此静态库的使用者将必须使用与您使用的编译器相同的编译器(如果您使用g ++,则他们将必须使用g ++)。

相反,如果您构建了一个dll(并正确地构建了它),那么您已经构建了一个完整的程序,无论使用什么编译器,所有使用者都可以使用。但是,如果需要跨编译器兼容性,则从dll导出有一些限制。


1
这对我来说是新闻。使用DLL时,交叉编译器有哪些限制?让程序员无需使用相同的工具链就可以构建DLL,这似乎是一个巨大的优势
Dan Dan

1
这个答案是有益的。补充说明:consumers of your static library will have to use the same compiler that you used如果静态库使用C ++库,例如#include <iostream>
trueadjustr

除非使用相同的编译器,否则不能使用c ++ dll(因为没有标准的c ++ abi,​​所以符号以不同的方式处理)。dll和客户端模块都必须使用相同的编译器和相同的构建设置
kcris

18

创建一个静态库

$$:~/static [32]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/static [33]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H

void foo();

#endif
$$:~/static [34]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/static [35]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H

void foo2();

#endif
$$:~/static [36]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/static [37]> cat makefile
hello: hello.o libtest.a
        cc -o hello hello.o -L. -ltest
hello.o: hello.c
        cc -c hello.c -I`pwd`
libtest.a:foo.o foo2.o
        ar cr libtest.a foo.o foo2.o
foo.o:foo.c
        cc -c foo.c
foo2.o:foo.c
        cc -c foo2.c
clean:
        rm -f foo.o foo2.o libtest.a hello.o

$$:~/static [38]>

创建一个动态库

$$:~/dynamic [44]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/dynamic [45]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H

void foo();

#endif
$$:~/dynamic [46]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/dynamic [47]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H

void foo2();

#endif
$$:~/dynamic [48]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/dynamic [49]> cat makefile
hello:hello.o libtest.sl
        cc -o hello hello.o -L`pwd` -ltest
hello.o:
        cc -c -b hello.c -I`pwd`
libtest.sl:foo.o foo2.o
        cc -G -b -o libtest.sl foo.o foo2.o
foo.o:foo.c
        cc -c -b foo.c
foo2.o:foo.c
        cc -c -b foo2.c
clean:
        rm -f libtest.sl foo.o foo

2.o hello.o
$$:~/dynamic [50]>

13

静态库被编译到客户端中。.lib在编译时使用,并且库的内容成为使用的可执行文件的一部分。

动态库在运行时加载,不会编译到客户端可执行文件中。动态库更加灵活,因为多个客户端可执行文件可以加载DLL并利用其功能。这也使客户端代码的整体大小和可维护性降至最低。


13

您应该仔细考虑随时间的变化,版本控制,稳定性,兼容性等。

如果有两个使用共享代码的应用程序,您是否要强制这些应用程序一起更改,以防它们需要彼此兼容?然后使用dll。所有的exe将使用相同的代码。

还是您想将它们彼此隔离,以便您可以更改一个并确信您没有破坏另一个。然后使用静态库。

DLL地狱是您可能应该使用静态库但您使用dll而不是所有的exe文件都兼容的情况。


9

必须将静态库链接到最终的可执行文件中。它成为可执行文件的一部分,并随处可见。每次执行可执行文件时,都会加载一个动态库,并且该动态库与DLL文件保持独立于可执行文件。

如果希望能够更改库提供的功能而不必重新链接可执行文件(只需替换DLL文件,而不必替换可执行文件),则可以使用DLL。

每当您没有理由使用动态库时,都将使用静态库。


当多个其他应用程序使用相同功能时,您也可能会使用DLL-这样可以减少占用空间。
蒂姆(Tim)2010年

同样,扩展您的初始概念,即“插件”体系结构,要在以后允许添加/未知功能而不必重新构建或重新发布,则只能使用动态库来完成。
蒂姆(Tim)2010年

8

Ulrich Drepper关于“ 如何编写共享库 ” 的论文也是很好的资源,详细介绍了如何最好地利用共享库或他所谓的“动态共享库”(DSO)。它着重于ELF二进制格式的共享库,但是一些讨论也适用于Windows DLL。



4

实际上(在一个大型项目中)要权衡的是初始加载时间,库将一次或一次被链接,必须做出的决定是链接将花费足够长的时间以使编译器需要咬住子弹并提前完成,或者动态链接器可以在加载时完成。


3

如果您的库要在多个可执行文件之间共享,则使其动态化以减小可执行文件的大小通常很有意义。否则,一定要使其静态。

使用dll有几个缺点。加载和卸载都有额外的开销。还存在其他依赖性。如果您更改dll以使其与您的可执行文件不兼容,它们将停止工作。另一方面,如果更改静态库,则使用旧版本的已编译可执行文件将不会受到影响。


3

如果库是静态的,则在链接时,代码将与可执行文件链接在一起。这使您的可执行文件更大(比起您使用动态路由而言)。

如果库是动态的,则在链接时,将对所需方法的引用内置到可执行文件中。这意味着您必须交付可执行文件和动态库。您还应该考虑对库中代码的共享访问是否是安全的,以及其他优先选择的加载地址。

如果可以使用静态库,请使用静态库。


3

我们在项目中使用了很多DLL(> 100)。这些DLL相互依赖,因此我们选择了动态链接的设置。但是,它具有以下缺点:

  • 启动缓慢(> 10秒)
  • 由于Windows会根据名称的唯一性加载模块,因此必须对DLL进行版本控制。否则,自己编写的组件将得到错误版本的DLL(即已加载的组件而不是其自己的分布式集合)
  • 优化器只能在DLL范围内进行优化。例如,优化器尝试将频繁使用的数据和代码彼此相邻放置,但这将无法跨DLL边界

也许更好的设置是使所有内容都成为静态库(因此,您只有一个可执行文件)。仅当没有代码重复发生时,此方法才有效。测试似乎支持该假设,但是我找不到MSDN的官方报价。例如,使用以下命令制作1个exe:

  • exe使用shared_lib1,shared_lib2
  • shared_lib1使用shared_lib2
  • shared_lib2

shared_lib2的代码和变量在最终合并的可执行文件中应该只出现一次。谁能支持这个问题?


您不是要以某种方式使用一些预编译器指令来避免代码重复吗?
Paceman

Afaiac预编译仅适用于每个模块(exe / dll / lib)。预编译主要是为了加快编译速度,尽管它还可以防止在编译单元中包含多个内容。但是,包含防护是实现此效果的更好方法。
gast128

2

静态库是包含链接库的目标代码的存档,当链接到应用程序时,该代码将被编译为可执行文件。共享库的不同之处在于它们没有编译到可执行文件中。相反,动态链接程序在某些目录中搜索所需的库,然后将其加载到内存中。一个以上的可执行文件可以同时使用同一共享库,从而减少了内存使用量和可执行文件的大小。但是,然后有更多文件与可执行文件一起分发。您需要确保将库安装在链接器可以找到它的使用系统上,静态链接可以消除此问题,但会导致可执行文件更大。


2

如果您在嵌入式项目或专用平台上工作,那么静态库是唯一的方法,那么很多时候,将它们编译到您的应用程序中就不那么麻烦了。同时拥有包含所有内容的项目和makefile可使生活更加幸福。


2

我有一个一般的经验法则,如果您有一个很大的代码库,它们都建立在较低级库(例如Utils或Gui框架)的基础上,您希望将这些库划分为更易于管理的库,然后将它们设置为静态库。动态库实际上并不会给您买任何东西,惊喜也很少-例如,只有一个单例实例。

如果您拥有一个与其余代码库完全独立的库(例如,第三方库),则可以考虑使其成为dll。如果该库是LGPL,由于许可条件,您可能仍需要使用dll。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.