当使用动态与静态图书馆
-
02-07-2019 - |
题
当创建一个类库C++,你可以选择之间的动态(.dll
, .so
)和静态(.lib
, .a
)图书馆。什么是它们之间的差别及时,这是适当使用哪个?
解决方案
静态库会增加二进制代码的大小。它们总是被加载,你编译的代码的任何版本都是将运行的代码的版本。
动态库分别存储和版本化。可能会加载一个动态库版本,该版本不是您的代码附带的原始版本如果该更新被认为与原始版本二进制兼容。
此外,动态库不一定要加载 - 它们通常在第一次调用时加载 - 并且可以在使用相同库的组件之间共享(多个数据加载,一个代码加载)。
动态库在大多数时候被认为是更好的方法,但最初他们有一个主要的缺陷(谷歌DLL地狱),它已经被更新的Windows操作系统(特别是Windows XP)淘汰了。
其他提示
其他人已经充分解释什么是静态的图书馆是的,但我想指出的一些注意事项的使用静态的图书馆,至少在窗口:
单: 如果有什么需要全球/静态和独特的,必须非常小心的把它放在一个静态的图书馆。如果多个Dll链接的对抗,静态图书馆,他们每人将得到他们自己的复制的单例。然而,如果应用程序是一个单一的EXE没有定义Dll,这可能不是一个问题。
未引用的代码,去除: 当你链接针对一个静态的图书馆,只有部分的静态图书馆引用的DLL/EXE将得到链接到你的DLL/EXE。
例如,如果
mylib.lib
包含a.obj
和b.obj
和你的DLL/EXE仅引用功能或变量a.obj
, ,全部的b.obj
会被丢弃的接头。如果b.obj
包含全球/静的对象,他们的构造和析构不会得到执行。如果这些构造/析构有副作用,你可能会感到失望通过他们的缺席。同样,如果静态的图书馆包含特殊entrypoints你可能需要照顾,他们实际上包括在内。这样的一个例子中的嵌入程(好吧,不Windows)将是一个中断的处理程序标记为在一个特定的地址。你还需要标记的中断处理程序作为一个入口点以确保它不会被丢弃。
另一个后果是,一个静态的图书馆可以包含对象的文件完全无法使用,由于未解决的引用,但它不会导致连接的错误,直到你参考的一个函数或可变从这些目文件。这可能会发生很久以后的图书馆被写。
调试的符号: 你可能会想要一个独立的PDB每个静态的图书馆,或者你可能想试的符号应放在目文件,以便他们得到卷入PDB DLL/EXE。视C++的文档说明 必要的选择.
RTTI: 你可以结束与多
type_info
对象为同类如果你的链接,一个静态的图书馆为多个Dll。如果你的计划假定type_info
是"单独"的数据和使用&typeid()
或type_info::before()
, 你可能不希望的和令人吃惊的结果。
Lib是一个单位的代码被捆绑在你的申请可执行的。
Dll是一个独立的单位的可执行代码。它加载过程中只有当一个电话是做到这代码。Dll可采用由多个应用程序和载在多个进程,同时仍然只有一份代码的硬盘驱动器。
Dll利弊:可用于重复使用/分享编码之间的若干产品;负荷过程中存在的需求和可以卸货时不需要;可以升级独立于其余的程序。
Dll弊:性能的影响dll装载和代码变基;版本控制的问题("dll地狱")
Lib利弊:没有性能的影响,因为码总是装的进程并不是罢了;没有版本控制的问题。
Lib弊:执行/工艺的"膨胀"-所有的代码是在你可执行的,并载在进程的开始;没有重复使用/分享--每种产品具有其自己的副本代码。
除了静态库和动态库的技术含义(静态文件捆绑一个大二进制文件中的所有内容与允许在几个不同可执行文件之间共享代码的动态库),还有法律含义。
例如,如果您使用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]>
C ++程序分两个阶段构建
- 编译 - 生成目标代码(.obj)
- 链接 - 生成可执行代码(.exe或.dll) 醇>
静态库(.lib)只是一个.obj文件包,因此不是一个完整的程序。它没有经历建立计划的第二个(链接)阶段。另一方面,Dlls就像exe一样,因此是完整的程序。
如果构建一个静态库,它还没有链接,因此静态库的使用者必须使用你使用的相同编译器(如果你使用g ++,他们将不得不使用g ++)。
相反,你构建了一个dll(并构建了它正确),您构建了一个完整的程序,所有消费者都可以使用,无论他们使用哪种编译器。但是,在从dll导出时,如果需要交叉编译器兼容性,则存在一些限制。
您应该仔细考虑随时间的变化,版本控制,稳定性,兼容性等。
如果有两个应用程序使用共享代码,您是否希望强制这些应用程序一起更改,以防它们需要相互兼容?然后使用dll。所有的exe都将使用相同的代码。
或者你想将它们彼此隔离开来,这样你就可以改变一个,并确信你没有打破另一个。然后使用静态库。
DLL地狱就是你可能应该使用静态库,但是你使用的是dll而不是所有的exes都可以使用它。
静态库被编译到客户端。在编译时使用.lib,并且库的内容成为使用可执行文件的一部分。
动态库在运行时加载,不会编译到客户端可执行文件中。动态库更灵活,因为多个客户端可执行文件可以加载DLL并利用其功能。这也可以将客户端代码的整体大小和可维护性降至最低。
必须将静态库链接到最终的可执行文件中;它成为可执行文件的一部分,无论它在哪里都跟随它。每次执行可执行文件时都会加载动态库,并将其作为DLL文件与可执行文件分开。
如果希望能够更改库提供的功能而不必重新链接可执行文件(只需替换DLL文件,而不必替换可执行文件),就可以使用DLL。
只要您没有理由使用动态库,就可以使用静态库。
有关此主题的精彩讨论,请阅读这篇文章来自Sun。
它具有所有好处,包括能够插入插入库。关于插入的更多细节可以在这篇文章中找到。
实际上你正在进行的交易(在一个大型项目中)处于初始加载时间,库将会在某个时间进行链接,必须做出的决定是链接需要足够长的时间编译器需要咬紧牙关并预先做好,或者动态链接器可以在加载时执行它。
如果您的库将在多个可执行文件之间共享,那么使其动态化以减小可执行文件的大小通常是有意义的。否则,一定要让它静止。
使用dll有几个缺点。加载和卸载它还有额外的开销。还有一个额外的依赖。如果您更改dll以使其与您的执行不兼容,它们将停止工作。另一方面,如果更改静态库,则使用旧版本的已编译可执行文件不会受到影响。
如果库是静态的,那么在链接时,代码将与您的可执行文件链接。这使得您的可执行文件更大(比您进入动态路由时)。
如果库是动态的,那么在链接时,对可执行文件内置了对所需方法的引用。这意味着您必须运送可执行文件和动态库。您还应该考虑对库中代码的共享访问是否安全,首选加载地址以及其他内容。
如果您可以使用静态库,请使用静态库。
静态库是包含库的目标代码的归档,当链接到应用程序时,代码被编译到可执行文件中。共享库的不同之处在于它们不会编译到可执行文件中。而是动态链接器搜索一些目录,寻找它需要的库,然后将其加载到内存中。 多个可执行文件可以同时使用同一个共享库,从而减少内存使用量和可执行文件大小。但是,随后可以使用可执行文件分发更多文件。您需要确保将库安装到链接器可以找到它的某个使用系统上,静态链接可以消除此问题,但会产生更大的可执行文件。
如果您在嵌入式项目或专用平台上工作静态库是唯一的方法,那么很多时候它们在编译到您的应用程序时也不那么麻烦。同时拥有包含所有内容的项目和makefile可以让生活更美好。
我们使用了大量的DLL's(>100)在我们的项目。这些DLL的相互依存的,因此,我们选择了建立动态联系起来。然而,它具有以下缺点:
- 慢启动(>10秒)
- DLL必须版本,因为windows载荷的模块上独特的名称。自己的书面件,否则会得到错误的版本DLL(即一个已经装载而不是其自己的分组)
- 优化只能优化内DLL边界。例如优化的尝试的地方频繁使用的数据和代码,但是这不会的工作跨越边界DLL
也许是一个更好的设置是为了让 的一切 静态图书馆(和因此你只有一个可执行).这工作只有如果没有代码重复发生。一个测试似乎支持这样的假设,但是我找不到官方的的报价。因此,例如使1exe:
- exe使用shared_lib1,shared_lib2
- shared_lib1使用shared_lib2
- shared_lib2
代码和变量的shared_lib2应该存在的最终合并的可执行只有一次。任何人都可以支持这个问题?
我会给出一个一般的经验法则,如果你有一个大的代码库,所有代码库都建立在较低级别的库(例如,Utils或Gui框架)之上,你想要将它们分成更易于管理的库,然后将它们设置为静态库。动态库并没有真正为你买任何东西而且意外就会减少 - 例如,只有一个单例实例。
如果您的库与代码库的其余部分完全分开(例如第三方库),那么请考虑将其设为dll。如果库是LGPL,则由于许可条件的原因,您可能还需要使用dll。