在C++中创建类库时,可以在动态(.dll
,.so
)和静态(.lib
,.a
)库之间进行选择.它们之间有什么区别,何时适合使用哪种?
静态库会增加二进制代码的大小.它们总是被加载,并且您编译的代码的任何版本都是将运行的代码的版本.
动态库分别存储和版本化.这是可能的动态库的版本被加载,这不是原来的一个与你的代码运,如果更新被认为与原始版本二进制兼容,则版本.
另外,动态库不一定要加载 - 它们通常在第一次调用时加载 - 并且可以在使用相同库的组件之间共享(多个数据加载,一个代码加载).
大多数时候动态库被认为是更好的方法,但最初他们有一个主要的缺陷(谷歌DLL地狱),它已被最近的Windows操作系统(特别是Windows XP)淘汰.
其他人已经充分解释了静态库是什么,但我想指出一些使用静态库的注意事项,至少在Windows上:
单身人士:如果某些东西需要全局/静态和独特,那么将它放在静态库中要非常小心.如果多个DLL链接到该静态库,则每个DLL都将获得它们自己的单例副本.但是,如果您的应用程序是一个没有自定义DLL的EXE,这可能不是问题.
未引用的代码删除:当您链接到静态库时,只有DLL/EXE引用的静态库部分才会链接到您的DLL/EXE中.
例如,如果mylib.lib
contains a.obj
和b.obj
您的DLL/EXE仅引用函数或变量a.obj
,b.obj
则链接器将丢弃整个函数或变量.如果b.obj
包含全局/静态对象,则它们的构造函数和析构函数将不会被执行.如果那些构造者/破坏者有副作用,你可能会因缺席而感到失望.
同样,如果静态库包含特殊入口点,您可能需要注意它们实际包含在内.嵌入式编程中的一个例子(好吧,不是Windows)将是一个标记为在特定地址的中断处理程序.您还需要将中断处理程序标记为入口点,以确保它不会被丢弃.
这样做的另一个后果是静态库可能包含由于未解析的引用而完全不可用的目标文件,但在从这些目标文件引用函数或变量之前,它不会导致链接器错误.这可能在写入库很久之后发生.
调试符号:您可能需要为每个静态库提供单独的PDB,或者您可能希望将调试符号放在目标文件中,以便它们可以滚动到DLL/EXE的PDB中.Visual C++文档解释了必要的选项.
RTTI:type_info
如果将单个静态库链接到多个DLL,则最终可能会为同一个类生成多个对象.如果您的程序假定type_info
是"单例"数据并使用&typeid()
或type_info::before()
,则可能会产生不良和令人惊讶的结果.
lib是捆绑在应用程序可执行文件中的代码单元.
dll是可执行代码的独立单元.只有在对该代码进行调用时才会在进程中加载它.dll可以由多个应用程序使用并加载到多个进程中,同时在硬盘驱动器上只有一个代码副本.
Dll专业人士:可用于在多个产品之间重用/共享代码; 按需加载进程内存,不需要时可以卸载; 可以独立于程序的其余部分进行升级.
Dll缺点:dll加载和代码重新定位的性能影响; 版本问题("dll hell")
Lib专业人员:没有性能影响,因为代码总是在进程中加载而且没有重新定位; 没有版本问题.
Lib cons:可执行文件/进程"膨胀" - 所有代码都在您的可执行文件中,并在进程启动时加载; 没有重用/共享 - 每个产品都有自己的代码副本.
除了静态库和动态库的技术含义(静态文件将一个大二进制文件中的所有内容与允许在几个不同可执行文件之间共享代码的动态库捆绑在一起)之外,还有法律含义.
例如,如果您使用的是LGPL许可的代码,你静态链接针对LGPL库(从而创造一个大的二进制),你的代码会自动变成开源的(免费为自由) LGPL代码.如果您链接到共享对象,那么您只需要LGPL对LGPL库本身所做的改进/错误修复.
如果您决定如何编译移动应用程序,这将成为一个非常重要的问题(在Android中,您可以选择静态与动态,在iOS中则不是 - 它始终是静态的).
C++程序分两个阶段构建
编译 - 生成目标代码(.obj)
链接 - 生成可执行代码(.exe或.dll)
静态库(.lib)只是一组.obj文件,因此不是一个完整的程序.它没有经历建立计划的第二个(链接)阶段.另一方面,Dlls就像exe一样,因此是完整的程序.
如果你构建一个静态库,它还没有链接,因此静态库的使用者必须使用你使用的相同编译器(如果你使用g ++,他们将不得不使用g ++).
如果您构建了一个dll(并且正确构建了它),那么无论使用哪种编译器,您都构建了一个所有使用者都可以使用的完整程序.但是,在从dll导出时,如果需要交叉编译器兼容性,则存在一些限制.
$$:~/static [32]> cat foo.c #includevoid 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 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 #include 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 #includevoid 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 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 #include 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]>
您应该仔细考虑随时间的变化,版本控制,稳定性,兼容性等.
如果有两个应用程序使用共享代码,您是否希望强制这些应用程序一起更改,以防它们需要相互兼容?然后使用dll.所有的exe都将使用相同的代码.
或者你想将它们彼此隔离开来,这样你就可以改变一个,并确信你没有打破另一个.然后使用静态库.
DLL地狱就是你可能应该使用静态库,但是你使用了一个dll而不是所有的exes都可以使用它.
静态库被编译到客户端.在编译时使用.lib,并且库的内容成为使用可执行文件的一部分.
动态库在运行时加载,不会编译到客户端可执行文件中.动态库更灵活,因为多个客户端可执行文件可以加载DLL并利用其功能.这还可以将客户端代码的总体大小和可维护性降至最低.
必须将静态库链接到最终的可执行文件中; 它成为可执行文件的一部分,无论它在哪里都跟随它.每次执行可执行文件时都会加载动态库,并将其作为DLL文件与可执行文件分开.
如果希望能够更改库提供的功能而不必重新链接可执行文件(只需替换DLL文件,而不必替换可执行文件),就可以使用DLL.
只要您没有理由使用动态库,就可以使用静态库.
Ulrich Drepper关于" 如何编写共享库 " 的论文也是一个很好的资源,详细说明了如何最好地利用共享库,或者他称之为"动态共享对象"(DSO).它更侧重于ELF二进制格式的共享库,但一些讨论也适用于Windows DLL.
有关此主题的精彩讨论,请阅读Sun 的这篇文章.
它具有所有好处,包括能够插入插入库.有关插入的更多细节可以在这里找到.