何时使用dynamic库和静态库
在C ++中创build类库时,可以selectdynamic(.dll)和静态(.lib)库。 他们之间有什么区别,什么时候使用哪一个是适当的?
静态库会增加二进制代码的大小。 它们总是被加载,你编译的代码的版本是将要运行的代码的版本。
dynamic库分别存储和版本化。 如果更新被视为与原始版本二进制兼容,那么dynamic库的一个版本可能会被加载,而不是您的代码附带的原始版本。
此外,dynamic库不一定会被加载 – 通常在首次调用时加载 – 并且可以在使用相同库(多个数据加载,一个代码加载)的组件之间共享。
dynamic库在大多数情况下被认为是更好的方法,但最初他们有一个主要缺陷(谷歌DLL地狱),它已被最近的Windows操作系统(尤其是Windows XP)所消除。
其他人已经充分解释了静态库是什么,但我想指出一些使用静态库的注意事项,至less在Windows上:
-
单身人士:如果某些东西需要全局性/静态性和独特性,那么把它放在一个静态库中时要非常小心。 如果多个DLL链接到这个静态库,他们将分别得到他们自己的单身副本。 但是,如果您的应用程序是没有自定义DLL的单个EXE,则这可能不成问题。
-
未引用的代码删除:当您链接到一个静态库时,只有由您的DLL / EXE引用的静态库部分将被链接到您的DLL / EXE中。
例如,如果
mylib.lib
包含a.obj
和b.obj
并且您的DLL / EXE仅引用来自a.obj
函数或variables,则a.obj
的整体将被链接器丢弃。 如果b.obj
包含全局/静态对象,则其构造函数和析构函数将不会被执行。 如果这些构造函数/析构函数有副作用,你可能会因为缺席而感到失望。同样,如果静态库包含特殊的入口点,则可能需要注意它们实际上包含在内。 embedded式编程(好吧,不是Windows)中的一个例子是一个中断处理程序,它被标记为处于特定的地址。 您还需要将中断处理程序标记为入口点,以确保它不会被丢弃。
这样做的另一个后果是静态库可能包含由于未parsing的引用而完全不可用的对象文件,但是直到从这些对象文件引用函数或variables之前,它不会导致链接器错误。 这可能会在图书馆被写入很长时间之后发生。
-
debugging符号:您可能需要为每个静态库分别创build一个PDB,或者您可能希望将debugging符号放置在对象文件中,以便将它们导入到DLL / EXE的PDB中。 Visual C ++文档解释了必要的选项 。
-
RTTI:如果将单个静态库链接到多个DLL,则最终可能会为同一个类使用多个
type_info
对象。 如果你的程序假设type_info
是“singleton”数据并且使用&typeid()
或者type_info::before()
,你可能会得到不希望的和令人惊讶的结果。
lib是捆绑在应用程序可执行文件中的代码单元。
一个DLL是一个独立的可执行代码单元。 只有当这个代码被调用的时候,它才会被加载。 一个DLL可以被多个应用程序使用并加载到多个进程中,同时在硬盘上仍然只有一个代码副本。
DLL专业人员 :可用于重用/共享几个产品之间的代码; 按需加载进程内存,不需要时可以卸载; 可以独立于程序的其余部分进行升级。
Dll缺点 :对dll加载和代码重新分配的性能影响; 版本问题(“dll hell”)
Lib优点 :不会影响性能,因为代码始终在进程中加载,不会重新分配; 没有版本控制问题。
Lib缺点 :可执行文件/进程“膨胀” – 所有的代码都在你的可执行文件中,并在进程启动时加载; 没有重用/共享 – 每个产品都有自己的代码副本。
创build一个静态库
$$:~/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]>
创build一个dynamic库
$$:~/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]>
除了静态和dynamic库的技术含义(静态文件将所有内容都包含在一个大的二进制文件中,而dynamic库允许在几个不同的可执行文件之间共享代码),还有法律上的含义 。
例如,如果您正在使用LGPL许可代码,并且将其与LGPL库进行静态链接(从而创build一个大二进制文件),则您的代码将自动变为开放式源代码( 免费) 。 如果您链接到共享对象,那么您只需要LGPL对LGPL库自身进行的改进/错误修复。
如果你决定如何编译你的移动应用程序(例如在Android中,你可以select静态和dynamic,在iOS中你不会 – 它总是静态的),这成为一个更为重要的问题。
您应该仔细考虑随时间的变化,版本,稳定性,兼容性等。
如果有两个应用程序使用共享代码,是否要强制这些应用程序一起更改,以防他们需要兼容? 然后使用dll。 所有的exe将使用相同的代码。
或者你想把他们隔离开来,这样你就可以改变一个,并确信你没有打破另一个。 然后使用静态库。
DLL地狱是当你可能应该使用一个静态库,但你使用了一个DLL,而不是所有的EXE都可以与它。
一个静态库被编译到客户端。 .lib在编译时使用,库的内容成为可执行文件的一部分。
dynamic库在运行时加载,而不是编译到客户端可执行文件中。 dynamic库更灵活,因为多个客户端可执行文件可以加载DLL并使用其function。 这也将您的客户代码的总体规模和可维护性降至最低。
C ++程序分两个阶段构build
- 编译 – 生成目标代码(.obj)
- 链接 – 生成可执行代码(.exe或.dll)
静态库(.lib)只是一个.obj文件包,因此不是一个完整的程序。 它没有经过第二阶段(连接)build立一个程序。 另一方面,Dll就像exe的,因此是完整的程序。
如果你构build一个静态库,它还没有被链接,因此你的静态库的用户将不得不使用你使用过的相同的编译器(如果你使用了g ++,他们将不得不使用g ++)。
如果您build立了一个dll(并且正确地构build了它),那么您已经构build了一个完整的程序,所有消费者都可以使用,无论他们使用哪个编译器。 有一些限制,虽然从dll导出,如果交叉编译器兼容性是需要的。
一个静态库必须链接到最终的可执行文件中; 它成为可执行文件的一部分,并随时随地执行。 每次执行可执行文件时都会加载一个dynamic库,并将其作为DLL文件与可执行文件保持分离。
当您想要更改库提供的function而不必重新链接可执行文件(只需replaceDLL文件,而不必更换可执行文件)就可以使用DLL。
只要您没有理由使用dynamic库,就可以使用静态库。
Ulrich Drepper关于“ 如何编写共享库 ”的论文也是很好的资源,详细介绍了如何最好地利用共享库,或者他称之为“dynamic共享对象”(DSO)。 它更侧重于ELF二进制格式的共享库,但是一些讨论也适用于Windows DLL。
有关这个主题的精彩讨论,请阅读Sun的这篇文章 。
它可以带来所有的好处,包括插入插入库。 有关介入的更多细节可以在这里find这篇文章 。
真的,你正在做的(在一个大的项目中)是在最初的加载时间,这些库将会在某个时候被链接,必须作出的决定是链接需要足够长的时间以至于编译器需要咬住子弹并在前面做,或者dynamic连接器可以在加载时执行它。
如果您的库将在多个可执行文件之间共享,那么使其变为dynamic以减小可执行文件的大小通常是有意义的。 否则,一定要把它变成静态的。
使用dll有几个缺点。 装载和卸载有额外的开销。 还有一个额外的依赖。 如果你改变了DLL以使其与你的executalbes不兼容,他们将停止工作。 另一方面,如果更改静态库,则使用旧版本的已编译可执行文件不会受到影响。
当链接到代码被编译到可执行文件的应用程序中时,静态库是包含库的目标代码的存档。 共享库的不同之处在于它们没有被编译到可执行文件中。 相反,dynamic链接器会search一些目录来查找所需的库,然后将其加载到内存中。 多于一个可执行文件可以同时使用相同的共享库,从而减less内存使用量和可执行文件大小。 但是,有更多的文件与可执行文件分发。 您需要确保该库安装在链接器可以find它的某个地方的使用系统上,静态链接消除了这个问题,但导致了一个更大的可执行文件。
如果库是静态的,那么在链接的时候代码被链接到你的可执行文件。 这使得你的可执行文件变得更大(比如你走dynamic路线)。
如果库是dynamic的,那么在链接时,对所需的方法的引用将embedded到您的可执行文件中。 这意味着你必须运送你的可执行文件和dynamic库。 你还应该考虑是否共享访问库中的代码是安全的,首选的加载地址等。
如果您可以使用静态库,请使用静态库。
如果你在embedded式项目或专用平台上工作,静态库是唯一的出路,但是很多时候他们不太麻烦编译到你的应用程序中。 还有包含一切的项目和makefile让生活更快乐。
我们在项目中使用了大量的DLL(> 100)。 这些DLL相互依赖,因此我们select了dynamic链接的设置。 但是它有以下缺点:
- 慢启动(> 10秒)
- DLL必须进行版本控制,因为Windows会根据名称的唯一性加载模块。 自己的书面组件会以其他方式获得DLL的错误版本(即已经加载而不是自己的分布式组件)
- 优化器只能在DLL边界内进行优化。 例如,优化器尝试将经常使用的数据和代码放在一起,但是这不适用于DLL边界
也许更好的设置是使一切都成为一个静态库(因此你只有一个可执行文件)。 这只有在没有代码重复发生的情况下才有效。 一个testing似乎支持这个假设,但我找不到一个正式的MSDN报价。 因此,例如使1 exe文件:
- exe使用shared_lib1,shared_lib2
- shared_lib1使用shared_lib2
- shared_lib2
shared_lib2的代码和variables只能在最终的合并的可执行文件中出现一次。 谁能支持这个问题?
如果你有一个很大的代码库,所有的代码都是build立在较低级别的库(例如一个Utils或者Gui框架)的基础之上的,你想把它们分割成更多可pipe理的库,然后把它们变成静态库。 dynamic库不会真的给你买东西,也不会有什么惊喜 – 比如只有一个单例。
如果你有一个完全独立于其他代码库的库(例如第三方库),那么考虑把它作为一个DLL。 如果图书馆是LGPL,由于许可条件,您可能需要使用DLL。