在C中创建类库时,可以在动态(.dll)和静态(.lib)库之间进行选择 . 它们之间有什么区别,何时适合使用哪种?
有关此主题的精彩讨论,请阅读Sun的this article .
它具有所有好处,包括能够插入插入库 . 关于插入的更多细节可以在this article here中找到 .
实际上你正在进行的交易(在一个大型项目中)处于初始加载时间,库将会在某个时间进行链接,必须做出的决定是链接需要足够长的时间以满足编译器的需要咬住子弹并预先做好,或动态链接器可以在加载时执行 .
Ulrich Drepper关于“How to Write Shared Libraries " is also good resource that details how best to take advantage of shared libraries, or what he refers to as "动态共享对象”(DSO)的论文 . 它更侧重于ELF二进制格式的共享库,但一些讨论也适用于Windows DLL .
静态库会增加二进制代码的大小 . 它们总是被加载,并且您编译的代码的任何版本都是将运行的代码的版本 .
动态库分别存储和版本化 . 它是您的代码附带的原始版本 if ,该更新被视为与原始版本二进制兼容 .
另外,动态库不一定要加载 - 它们通常在第一次调用时加载 - 并且可以在使用相同库的组件之间共享(多个数据加载,一个代码加载) .
大多数时候动态库被认为是更好的方法,但最初他们有一个主要的缺陷(谷歌DLL地狱),它已被最近的Windows操作系统(特别是Windows XP)淘汰 .
其他人已经充分解释了静态库是什么,但我想指出一些使用静态库的注意事项,至少在Windows上:
Singletons: 如果某些东西需要全局/静态和唯一,那么将它放在静态库中要非常小心 . 如果多个DLL链接到该静态库,则每个DLL都将获得它们自己的单例副本 . 但是,如果您的应用程序是一个没有自定义DLL的EXE,这可能不是问题 .
Unreferenced code removal: 当您链接到静态库时,只有DLL / EXE引用的静态库部分才会链接到您的DLL / EXE中 .
例如,如果 mylib.lib 包含 a.obj 和 b.obj 且您的DLL / EXE仅引用 a.obj 中的函数或变量,则链接器将丢弃整个 b.obj . 如果 b.obj 包含全局/静态对象,则它们的构造函数和析构函数将不会被执行 . 如果那些构造者/破坏者有副作用,你可能会因缺席而感到失望 .
mylib.lib
a.obj
b.obj
同样,如果静态库包含特殊入口点,您可能需要注意它们实际包含在内 . 嵌入式编程中的一个例子(好吧,不是Windows)将是一个标记为在特定地址的中断处理程序 . 您还需要将中断处理程序标记为入口点,以确保它不会被丢弃 .
这样做的另一个后果是静态库可能包含由于未解析的引用而完全不可用的目标文件,但在从这些目标文件引用函数或变量之前,它不会导致链接器错误 . 这可能在写入库很久之后发生 .
Debug symbols: 您可能需要为每个静态库提供单独的PDB,或者您可能希望将调试符号放在目标文件中,以便它们可以滚动到DLL / EXE的PDB中 . Visual C文档解释the necessary options .
RTTI: 如果将单个静态库链接到多个DLL,则最终可能会为同一个类生成多个 type_info 对象 . 如果您的程序假定 type_info 是"singleton"数据并且使用 &typeid() 或 type_info::before() ,则可能会得到不合需要且令人惊讶的结果 .
type_info
&typeid()
type_info::before()
lib是捆绑在应用程序可执行文件中的代码单元 .
dll是可执行代码的独立单元 . 只有在对该代码进行调用时才会在进程中加载它 . dll可以由多个应用程序使用并加载到多个进程中,同时在硬盘驱动器上只有一个代码副本 .
Dll pros :可用于在多个产品之间重用/共享代码;按需加载进程内存,不需要时可以卸载;可以独立于程序的其余部分进行升级 .
Dll cons :dll加载和代码重新定位对性能的影响;版本问题("dll hell")
Lib pros :没有性能影响,因为代码总是在进程中加载而且没有重新定位;没有版本问题 .
Lib cons :executable / process "bloat" - 所有代码都在您的可执行文件中,并在进程启动时加载;没有重用/共享 - 每个产品都有自己的代码副本 .
除了静态库和动态库的技术含义(静态文件将一个大二进制文件中的所有内容与允许在几个不同可执行文件之间共享代码的动态库捆绑在一起)之外,还有法律含义 .
对于例如,如果您正在使用LGPL许可代码并且您静态链接到LGPL库(从而创建一个大二进制文件),您的代码将自动变为开源(free as in freedom) 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]>
C程序分两个阶段构建
编译 - 生成目标代码(.obj)
链接 - 生成可执行代码(.exe或.dll)
静态库(.lib)只是一组.obj文件,因此不是一个完整的程序 . 它没有经历 Build 计划的第二个(链接)阶段 . 另一方面,Dlls就像exe一样,因此是完整的程序 .
如果你构建一个静态库,它还没有链接,因此静态库的使用者必须使用你使用的相同编译器(如果你使用g,他们将不得不使用g) .
如果您构建了一个dll(并构建它correctly),那么无论使用哪种编译器,您都构建了一个所有消费者都可以使用的完整程序 . 但是,在从dll导出时,如果需要交叉编译器兼容性,则存在一些限制 .
您应该仔细考虑随时间的变化,版本控制,稳定性,兼容性等 .
如果有两个应用程序使用共享代码,您是否希望强制这些应用程序一起更改,以防它们需要相互兼容?然后使用dll . 所有的exe都将使用相同的代码 .
或者你想将它们彼此隔离开来,这样你就可以改变一个,并确信你没有打破另一个 . 然后使用静态库 .
DLL地狱就是你可能应该使用静态库,但是你使用了一个dll而不是所有的exes都可以使用它 .
静态库被编译到客户端 . 在编译时使用.lib,并且库的内容成为使用可执行文件的一部分 .
动态库在运行时加载,不会编译到客户端可执行文件中 . 动态库更灵活,因为多个客户端可执行文件可以加载DLL并利用其功能 . 这还可以将客户端代码的总体大小和可维护性降至最低 .
必须将静态库链接到最终的可执行文件中;它成为可执行文件的一部分,无论它在哪里都跟随它 . 每次执行可执行文件时都会加载动态库,并将其作为DLL文件与可执行文件分开 .
如果希望能够更改库提供的功能而不必重新链接可执行文件(只需替换DLL文件,而不必替换可执行文件),就可以使用DLL .
只要您没有理由使用动态库,就可以使用静态库 .
如果您的库将在多个可执行文件之间共享,那么使其动态化以减小可执行文件的大小通常是有意义的 . 否则,一定要让它静止 .
使用dll有几个缺点 . 加载和卸载它还有额外的开销 . 还有一个额外的依赖 . 如果您更改dll以使其与您的执行不兼容,它们将停止工作 . 另一方面,如果更改静态库,则使用旧版本的已编译可执行文件不会受到影响 .
如果库是静态的,然后在链接时,代码与您的可执行文件链接 . 这使您的可执行文件更大(比您进入动态路由) .
如果库是动态的,那么在链接时,对可执行文件内置了对所需方法的引用 . 这意味着您必须运送可执行文件和动态库 . 您还应该考虑对库中代码的共享访问是否安全,首选加载地址以及其他内容 .
如果您可以使用静态库,请使用静态库 .
静态库是包含库的目标代码的归档,当链接到将代码编译到可执行文件中的应用程序时 . 共享库的不同之处在于它们不会编译到可执行文件中 . 而是动态链接器搜索一些目录,寻找它需要的库,然后将其加载到内存中 . 多个可执行文件可以同时使用同一个共享库,从而减少内存使用量和可执行文件大小 . 但是,随后可以使用可执行文件分发更多文件 . 您需要确保将库安装到链接器可以找到它的某个使用系统上,静态链接可以消除此问题,但会导致更大的可执行文件 .
如果您在嵌入式项目或专用平台上工作静态库是唯一的方法,那么很多时候它们在编译到您的应用程序时也不那么麻烦 . 同时拥有包含所有内容的项目和makefile可以让生活更快乐 .
我们在项目中使用了很多DLL(> 100) . 这些DLL彼此依赖,因此我们选择了动态链接的设置 . 但它有以下缺点:
慢启动(> 10秒)
DLL必须进行版本控制,因为Windows会在名称的唯一性上加载模块 . 否则,自己编写的组件将获得错误版本的DLL(即已加载的DLL而不是其自己的分布式集)
优化器只能在DLL边界内进行优化 . 例如,优化器尝试将经常使用的数据和代码放在一起,但这不适用于DLL边界
也许更好的设置是使 everything 成为一个静态库(因此你只有一个可执行文件) . 这仅在没有发生代码重复时才有效 . 测试似乎支持这个假设,但我找不到正式的MSDN引用 . 所以例如make 1 exe用:
exe使用shared_lib1,shared_lib2
shared_lib1使用shared_lib2
shared_lib2
shared_lib2的代码和变量应该只出现在最终合并的可执行文件中一次 . 有谁能支持这个问题?
我给出了一个通用的经验法则,如果你有一个大的代码库,所有代码库都 Build 在较低级别的库(例如,Utils或Gui框架)之上,你想要将它们分成更易于管理的库,然后将它们作为静态库 . 动态库并没有真正为你买任何东西而且意外就会减少 - 例如,只有一个单例实例 .
如果您的库与代码库的其余部分完全分开(例如第三方库),那么请考虑将其设为dll . 如果库是LGPL,您可能需要使用dll,因为许可条件 .
18 回答
有关此主题的精彩讨论,请阅读Sun的this article .
它具有所有好处,包括能够插入插入库 . 关于插入的更多细节可以在this article here中找到 .
实际上你正在进行的交易(在一个大型项目中)处于初始加载时间,库将会在某个时间进行链接,必须做出的决定是链接需要足够长的时间以满足编译器的需要咬住子弹并预先做好,或动态链接器可以在加载时执行 .
Ulrich Drepper关于“How to Write Shared Libraries " is also good resource that details how best to take advantage of shared libraries, or what he refers to as "动态共享对象”(DSO)的论文 . 它更侧重于ELF二进制格式的共享库,但一些讨论也适用于Windows DLL .
静态库会增加二进制代码的大小 . 它们总是被加载,并且您编译的代码的任何版本都是将运行的代码的版本 .
动态库分别存储和版本化 . 它是您的代码附带的原始版本 if ,该更新被视为与原始版本二进制兼容 .
另外,动态库不一定要加载 - 它们通常在第一次调用时加载 - 并且可以在使用相同库的组件之间共享(多个数据加载,一个代码加载) .
大多数时候动态库被认为是更好的方法,但最初他们有一个主要的缺陷(谷歌DLL地狱),它已被最近的Windows操作系统(特别是Windows XP)淘汰 .
其他人已经充分解释了静态库是什么,但我想指出一些使用静态库的注意事项,至少在Windows上:
Singletons: 如果某些东西需要全局/静态和唯一,那么将它放在静态库中要非常小心 . 如果多个DLL链接到该静态库,则每个DLL都将获得它们自己的单例副本 . 但是,如果您的应用程序是一个没有自定义DLL的EXE,这可能不是问题 .
Unreferenced code removal: 当您链接到静态库时,只有DLL / EXE引用的静态库部分才会链接到您的DLL / EXE中 .
例如,如果
mylib.lib
包含a.obj
和b.obj
且您的DLL / EXE仅引用a.obj
中的函数或变量,则链接器将丢弃整个b.obj
. 如果b.obj
包含全局/静态对象,则它们的构造函数和析构函数将不会被执行 . 如果那些构造者/破坏者有副作用,你可能会因缺席而感到失望 .同样,如果静态库包含特殊入口点,您可能需要注意它们实际包含在内 . 嵌入式编程中的一个例子(好吧,不是Windows)将是一个标记为在特定地址的中断处理程序 . 您还需要将中断处理程序标记为入口点,以确保它不会被丢弃 .
这样做的另一个后果是静态库可能包含由于未解析的引用而完全不可用的目标文件,但在从这些目标文件引用函数或变量之前,它不会导致链接器错误 . 这可能在写入库很久之后发生 .
Debug symbols: 您可能需要为每个静态库提供单独的PDB,或者您可能希望将调试符号放在目标文件中,以便它们可以滚动到DLL / EXE的PDB中 . Visual C文档解释the necessary options .
RTTI: 如果将单个静态库链接到多个DLL,则最终可能会为同一个类生成多个
type_info
对象 . 如果您的程序假定type_info
是"singleton"数据并且使用&typeid()
或type_info::before()
,则可能会得到不合需要且令人惊讶的结果 .lib是捆绑在应用程序可执行文件中的代码单元 .
dll是可执行代码的独立单元 . 只有在对该代码进行调用时才会在进程中加载它 . dll可以由多个应用程序使用并加载到多个进程中,同时在硬盘驱动器上只有一个代码副本 .
Dll pros :可用于在多个产品之间重用/共享代码;按需加载进程内存,不需要时可以卸载;可以独立于程序的其余部分进行升级 .
Dll cons :dll加载和代码重新定位对性能的影响;版本问题("dll hell")
Lib pros :没有性能影响,因为代码总是在进程中加载而且没有重新定位;没有版本问题 .
Lib cons :executable / process "bloat" - 所有代码都在您的可执行文件中,并在进程启动时加载;没有重用/共享 - 每个产品都有自己的代码副本 .
除了静态库和动态库的技术含义(静态文件将一个大二进制文件中的所有内容与允许在几个不同可执行文件之间共享代码的动态库捆绑在一起)之外,还有法律含义 .
对于例如,如果您正在使用LGPL许可代码并且您静态链接到LGPL库(从而创建一个大二进制文件),您的代码将自动变为开源(free as in freedom) LGPL代码 . 如果您链接到共享对象,那么您只需要LGPL您对LGPL库本身所做的改进/错误修复 .
如果您决定如何编译移动应用程序,这将成为一个非常重要的问题(在Android中,您可以选择静态与动态,在iOS中则不是 - 它始终是静态的) .
创建静态库
创建动态库
C程序分两个阶段构建
编译 - 生成目标代码(.obj)
链接 - 生成可执行代码(.exe或.dll)
静态库(.lib)只是一组.obj文件,因此不是一个完整的程序 . 它没有经历 Build 计划的第二个(链接)阶段 . 另一方面,Dlls就像exe一样,因此是完整的程序 .
如果你构建一个静态库,它还没有链接,因此静态库的使用者必须使用你使用的相同编译器(如果你使用g,他们将不得不使用g) .
如果您构建了一个dll(并构建它correctly),那么无论使用哪种编译器,您都构建了一个所有消费者都可以使用的完整程序 . 但是,在从dll导出时,如果需要交叉编译器兼容性,则存在一些限制 .
您应该仔细考虑随时间的变化,版本控制,稳定性,兼容性等 .
如果有两个应用程序使用共享代码,您是否希望强制这些应用程序一起更改,以防它们需要相互兼容?然后使用dll . 所有的exe都将使用相同的代码 .
或者你想将它们彼此隔离开来,这样你就可以改变一个,并确信你没有打破另一个 . 然后使用静态库 .
DLL地狱就是你可能应该使用静态库,但是你使用了一个dll而不是所有的exes都可以使用它 .
静态库被编译到客户端 . 在编译时使用.lib,并且库的内容成为使用可执行文件的一部分 .
动态库在运行时加载,不会编译到客户端可执行文件中 . 动态库更灵活,因为多个客户端可执行文件可以加载DLL并利用其功能 . 这还可以将客户端代码的总体大小和可维护性降至最低 .
必须将静态库链接到最终的可执行文件中;它成为可执行文件的一部分,无论它在哪里都跟随它 . 每次执行可执行文件时都会加载动态库,并将其作为DLL文件与可执行文件分开 .
如果希望能够更改库提供的功能而不必重新链接可执行文件(只需替换DLL文件,而不必替换可执行文件),就可以使用DLL .
只要您没有理由使用动态库,就可以使用静态库 .
如果您的库将在多个可执行文件之间共享,那么使其动态化以减小可执行文件的大小通常是有意义的 . 否则,一定要让它静止 .
使用dll有几个缺点 . 加载和卸载它还有额外的开销 . 还有一个额外的依赖 . 如果您更改dll以使其与您的执行不兼容,它们将停止工作 . 另一方面,如果更改静态库,则使用旧版本的已编译可执行文件不会受到影响 .
如果库是静态的,然后在链接时,代码与您的可执行文件链接 . 这使您的可执行文件更大(比您进入动态路由) .
如果库是动态的,那么在链接时,对可执行文件内置了对所需方法的引用 . 这意味着您必须运送可执行文件和动态库 . 您还应该考虑对库中代码的共享访问是否安全,首选加载地址以及其他内容 .
如果您可以使用静态库,请使用静态库 .
静态库是包含库的目标代码的归档,当链接到将代码编译到可执行文件中的应用程序时 . 共享库的不同之处在于它们不会编译到可执行文件中 . 而是动态链接器搜索一些目录,寻找它需要的库,然后将其加载到内存中 . 多个可执行文件可以同时使用同一个共享库,从而减少内存使用量和可执行文件大小 . 但是,随后可以使用可执行文件分发更多文件 . 您需要确保将库安装到链接器可以找到它的某个使用系统上,静态链接可以消除此问题,但会导致更大的可执行文件 .
如果您在嵌入式项目或专用平台上工作静态库是唯一的方法,那么很多时候它们在编译到您的应用程序时也不那么麻烦 . 同时拥有包含所有内容的项目和makefile可以让生活更快乐 .
我们在项目中使用了很多DLL(> 100) . 这些DLL彼此依赖,因此我们选择了动态链接的设置 . 但它有以下缺点:
慢启动(> 10秒)
DLL必须进行版本控制,因为Windows会在名称的唯一性上加载模块 . 否则,自己编写的组件将获得错误版本的DLL(即已加载的DLL而不是其自己的分布式集)
优化器只能在DLL边界内进行优化 . 例如,优化器尝试将经常使用的数据和代码放在一起,但这不适用于DLL边界
也许更好的设置是使 everything 成为一个静态库(因此你只有一个可执行文件) . 这仅在没有发生代码重复时才有效 . 测试似乎支持这个假设,但我找不到正式的MSDN引用 . 所以例如make 1 exe用:
exe使用shared_lib1,shared_lib2
shared_lib1使用shared_lib2
shared_lib2
shared_lib2的代码和变量应该只出现在最终合并的可执行文件中一次 . 有谁能支持这个问题?
我给出了一个通用的经验法则,如果你有一个大的代码库,所有代码库都 Build 在较低级别的库(例如,Utils或Gui框架)之上,你想要将它们分成更易于管理的库,然后将它们作为静态库 . 动态库并没有真正为你买任何东西而且意外就会减少 - 例如,只有一个单例实例 .
如果您的库与代码库的其余部分完全分开(例如第三方库),那么请考虑将其设为dll . 如果库是LGPL,您可能需要使用dll,因为许可条件 .