何时使用动态库与静态库
在C ++中创建类库时,可以在动态(.dll)库和静态(.lib)库之间进行选择。它们之间有什么区别,什么时候适合使用?
解决方案
如果库要在多个可执行文件之间共享,则使其动态化以减小可执行文件的大小通常很有意义。否则,一定要使其静态。
使用dll有几个缺点。加载和卸载它都有额外的开销。还有一个添加的依赖项。如果我们更改了dll以使其与可执行文件不兼容,它们将停止工作。另一方面,如果更改静态库,则使用旧版本的已编译可执行文件将不会受到影响。
静态库被编译到客户端中。 .lib在编译时使用,并且库的内容成为使用的可执行文件的一部分。
动态库在运行时加载,不会编译到客户端可执行文件中。动态库更加灵活,因为多个客户端可执行文件可以加载DLL并利用其功能。这也使客户端代码的整体大小和可维护性降至最低。
静态库会增加二进制文件中代码的大小。它们始终会被加载,并且我们使用的任何编译版本的代码都是将运行的代码版本。
动态库分别存储和版本控制。如果更新被认为与原始版本二进制兼容,则有可能加载的动态库版本不是代码随附的原始版本。
另外,动态库不一定要加载-通常在首次调用时就加载-并且可以在使用同一库的组件之间共享(多个数据加载,一个代码加载)。
在大多数情况下,动态库被认为是更好的方法,但最初它们有一个重大缺陷(谷歌DLL地狱),但最近的Windows操作系统(尤其是Windows XP)已将其消除。
必须将静态库链接到最终的可执行文件中。它成为可执行文件的一部分,并随处可见。每次执行可执行文件时,都会加载一个动态库,并且该动态库与DLL文件保持独立于可执行文件。
如果希望能够更改库提供的功能而不必重新链接可执行文件(只需替换DLL文件,而不必替换可执行文件),则可以使用DLL。
每当我们没有理由使用动态库时,就应该使用静态库。
静态库是包含链接库的目标代码的存档,当链接到应用程序时,该代码将被编译为可执行文件。共享库的不同之处在于它们没有被编译到可执行文件中。相反,动态链接程序在某些目录中搜索所需的库,然后将其加载到内存中。
一个以上的可执行文件可以同时使用同一共享库,从而减少了内存使用量和可执行文件的大小。但是,然后有更多文件与可执行文件一起分发。我们需要确保将库安装在链接器可以找到它的使用系统上,静态链接可以消除此问题,但是会导致更大的可执行文件。
如果库是静态的,则在链接时,代码将与可执行文件链接在一起。这使可执行文件更大(比起我们使用动态路由而言)。
如果库是动态的,则在链接时,将对所需方法的引用内置到可执行文件中。这意味着我们必须交付可执行文件和动态库。我们还应该考虑对库中代码的共享访问是否安全,在其他方面是首选的加载地址。
如果可以使用静态库,请使用静态库。
库是捆绑在应用程序可执行文件中的代码单位。
dll是可执行代码的独立单元。仅当对该代码进行调用时,才将其加载到进程中。一个dll可以由多个应用程序使用,并可以在多个进程中加载,同时在硬盘驱动器上仍然只有一个代码副本。
DLL专业人士:可用于在多个产品之间重用/共享代码;根据需要将其加载到过程存储器中,并且在不需要时可以将其卸载;可以独立于程序的其余部分进行升级。
DLL的缺点:DLL加载和代码重新基准化对性能的影响;版本问题(" dll地狱")
易于使用的库:对性能没有影响,因为代码始终在过程中加载并且不会重新建立基础;没有版本问题。
缺点:可执行文件/进程"膨胀"所有代码在可执行文件中,并在进程启动时加载;禁止重复使用/共享每个产品都有其自己的代码副本。
如果我们在嵌入式项目或者专用平台上工作,那么静态库是唯一的方法,那么很多时候它们就不那么麻烦地编译到应用程序中了。同时拥有包含所有内容的项目和makefile可使生活更加幸福。
我们应该仔细考虑随时间的变化,版本控制,稳定性,兼容性等。
如果有两个使用共享代码的应用程序,我们是否要强制这些应用程序一起更改,以防它们需要彼此兼容?然后使用dll。所有的exe都将使用相同的代码。
还是我们想将它们彼此隔离,以便我们可以更改一个并确信我们没有破坏另一个。然后使用静态库。
DLL地狱是我们可能应该使用静态库,但我们使用的是dll,并且并非所有exe都兼容的情况。
实际上(在一个大型项目中)要权衡的是初始加载时间,库将一次或者一次被链接,必须做出的决定是链接将花费足够长的时间,编译器需要咬住子弹并提前完成,或者动态链接器可以在加载时完成。
其他人已经充分解释了什么是静态库,但是我想指出至少在Windows上使用静态库的一些注意事项:
- 单例:如果某些事物需要是全局/静态和唯一的,则在将其放入静态库时要格外小心。如果将多个DLL链接到该静态库,则它们将各自获得自己的单例副本。但是,如果应用程序是没有自定义DLL的单个EXE,则这可能不是问题。
- 未引用的代码删除:当我们链接到静态库时,只有静态库中由DLL / EXE引用的部分才会链接到DLL / EXE中。例如,如果mylib.lib包含a.obj和b.obj,并且DLL / EXE仅引用a.obj中的函数或者变量,则b.obj的全部将被丢弃。链接器。如果b.obj包含全局/静态对象,则不会执行其构造函数和析构函数。如果这些构造函数/析构函数具有副作用,那么我们可能会对它们的缺失感到失望。同样,如果静态库包含特殊的入口点,则可能需要注意它们确实包含在内。嵌入式编程(好的,不是Windows)中的一个例子是一个被标记为位于特定地址的中断处理程序。我们还需要将中断处理程序标记为入口点,以确保不会被丢弃。这样做的另一个结果是,静态库可能包含由于无法解析的引用而完全无法使用的目标文件,但是在我们从这些目标文件中引用函数或者变量之前,它不会导致链接器错误。编写库后可能会发生这种情况。
- 调试符号:我们可能希望为每个静态库使用一个单独的PDB,或者可能希望将调试符号放置在目标文件中,以便将它们导入DLL / EXE的PDB中。 Visual C ++文档介绍了必要的选项。
- RTTI:如果我们将单个静态库链接到多个DLL,则可能会为同一类使用多个
type_info
对象。如果程序假定type_info
是"单个"数据并使用&typeid()
或者type_info :: before()
,则可能会得到令人不快且令人惊讶的结果。
要对该主题进行出色的讨论,请阅读Sun的这篇文章。
它具有所有优点,包括能够插入中介库。可以在本文中找到有关插入的更多详细信息。
Ulrich Drepper关于"如何编写共享库"的论文也是很好的资源,详细介绍了如何最好地利用共享库或者他所谓的"动态共享库"(DSO)。它着重于ELF二进制格式的共享库,但是一些讨论也适用于Windows DLL。
除了静态库与动态库的技术含义(静态文件将所有内容捆绑在一个大的二进制库与动态库中,它允许在多个不同的可执行文件之间共享代码)一起,还涉及法律方面的问题。
例如,如果我们使用的是LGPL许可代码,并且与LGPL库进行静态链接(从而创建一个大的二进制文件),则代码会自动变为开源(自由自由)LGPL代码。如果链接到共享库,则只需要LGPL对LGPL库本身所做的改进/错误修复。
例如,如果我们要决定如何编译移动应用程序,则这将成为一个更为重要的问题(在Android中,我们可以选择静态还是动态,在iOS中,我们并不总是静态的)。
创建一个静态库
$$:~/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]>