何时使用动态库和静态库

时间:2008-09-26 15:02:02

标签: c++ dll shared-libraries static-linking dynamic-linking

在C ++中创建类库时,您可以选择动态(.dll.so)和静态(.lib.a)库。它们之间有什么区别,何时适合使用哪种?

18 个答案:

答案 0 :(得分:281)

静态库会增加二进制代码的大小。它们总是被加载,你编译的代码的任何版本都是将运行的代码的版本。

动态库分别存储和版本化。可能会加载一个动态库版本,该版本不是您的代码附带的原始版本如果该更新被认为与原始版本二进制兼容。

此外,动态库不一定要加载 - 它们通常在第一次调用时加载 - 并且可以在使用相同库的组件之间共享(多个数据加载,一个代码加载)。

动态库在大多数时候被认为是更好的方法,但最初他们有一个主要的缺陷(谷歌DLL地狱),它已经被更新的Windows操作系统(特别是Windows XP)淘汰了。

答案 1 :(得分:183)

其他人已经充分解释了静态库是什么,但我想指出一些使用静态库的注意事项,至少在Windows上:

  • 单身人士:如果某些内容需要全局/静态且唯一,请务必将其放入静态库中。如果多个DLL链接到该静态库,则每个DLL都将获得它们自己的单例副本。但是,如果您的应用程序是单个EXE而没有自定义DLL,则这可能不是问题。

  • 取消引用代码:当您链接到静态库时,只有DLL / EXE引用的静态库部分才会链接到您的DLL / EXE中。

    例如,如果mylib.lib包含a.objb.obj且您的DLL / EXE仅引用a.obj中的函数或变量,则b.obj将包含被链接器丢弃。如果b.obj包含全局/静态对象,则它们的构造函数和析构函数将不会被执行。如果那些构造函数/析构函数有副作用,你可能会对它们缺席感到失望。

    同样,如果静态库包含特殊入口点,您可能需要注意它们实际包含在内。嵌入式编程中的一个例子(好吧,不是Windows)将是一个标记为在特定地址的中断处理程序。您还需要将中断处理程序标记为入口点,以确保它不会被丢弃。

    这样做的另一个后果是静态库可能包含由于未解析的引用而完全不可用的目标文件,但在从这些目标文件引用函数或变量之前,它不会导致链接器错误。这可能在写入库很久之后发生。

  • 调试符号:您可能需要为每个静态库提供单独的PDB,或者您可能希望将调试符号放在目标文件中,以便它们可以滚动到PDB中DLL / EXE。 Visual C ++文档解释了the necessary options

  • RTTI:如果将单个静态库链接到多个DLL,则最终可能会为同一个类生成多个type_info个对象。如果您的计划假定type_info是“单身”数据并使用&typeid()type_info::before(),则可能会产生不良和令人惊讶的结果。

答案 2 :(得分:59)

lib是捆绑在应用程序可执行文件中的代码单元。

dll是可执行代码的独立单元。只有在对该代码进行调用时才会在进程中加载​​它。 dll可以被多个应用程序使用并加载到多个进程中,而硬盘驱动器上只有一个代码副本。

Dll专业人员:可用于在多个产品之间重用/共享代码;按需加载进程内存,不需要时可以卸载;可以独立于程序的其余部分进行升级。

Dll缺点:dll加载和代码重新定位对性能的影响;版本问题(“dll hell”)

Lib专业人员:没有性能影响,因为代码总是在流程中加载而且没有重新定位;没有版本问题。

Lib cons :可执行/进程“膨胀” - 所有代码都在您的可执行文件中,并在进程启动时加载;没有重用/共享 - 每个产品都有自己的代码副本。

答案 3 :(得分:21)

除了静态库和动态库的技术含义(静态文件捆绑一个大二进制文件中的所有内容与允许在几个不同可执行文件之间共享代码的动态库),还有法律含义

例如,如果您使用LGPL许可代码并且静态链接到LGPL库(从而创建一个大二进制文件),您的代码将自动变为开源(free as in freedom) LGPL代码。如果您链接到共享对象,那么您只需要LGPL对LGPL库本身所做的改进/错误修复。

如果您决定如何编译移动应用程序,这将成为一个更重要的问题(在Android中,您可以选择静态与动态,在iOS中则不是 - 它始终是静态的)。

答案 4 :(得分:18)


创建静态库

$$:~/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]>

答案 5 :(得分:17)

C ++程序分两个阶段构建

  1. 编译 - 生成目标代码(.obj)
  2. 链接 - 生成可执行代码(.exe或.dll)
  3. 静态库(.lib)只是一个.obj文件包,因此不是一个完整的程序。它没有经历建立计划的第二个(链接)阶段。另一方面,Dlls就像exe一样,因此是完整的程序。

    如果构建一个静态库,它还没有链接,因此静态库的使用者必须使用你使用的相同编译器(如果你使用g ++,他们将不得不使用g ++)。

    如果您构建了一个dll(并构建了它correctly),那么无论使用哪种编译器,您都构建了一个所有消费者都可以使用的完整程序。但是,在从dll导出时,如果需要交叉编译器兼容性,则存在一些限制。

答案 6 :(得分:12)

您应该仔细考虑随时间的变化,版本控制,稳定性,兼容性等。

如果有两个应用程序使用共享代码,您是否希望强制这些应用程序一起更改,以防它们需要相互兼容?然后使用dll。所有的exe都将使用相同的代码。

或者你想将它们彼此隔离开来,这样你就可以改变一个,并确信你没有打破另一个。然后使用静态库。

DLL地狱就是你可能应该使用静态库,但是你使用的是dll而不是所有的exes都可以使用它。

答案 7 :(得分:11)

静态库被编译到客户端。在编译时使用.lib,并且库的内容成为使用可执行文件的一部分。

动态库在运行时加载,不会编译到客户端可执行文件中。动态库更灵活,因为多个客户端可执行文件可以加载DLL并利用其功能。这也可以将客户端代码的整体大小和可维护性降至最低。

答案 8 :(得分:9)

必须将静态库链接到最终的可执行文件中;它成为可执行文件的一部分,无论它在哪里都跟随它。每次执行可执行文件时都会加载动态库,并将其作为DLL文件与可执行文件分开。

如果希望能够更改库提供的功能而不必重新链接可执行文件(只需替换DLL文件,而不必替换可执行文件),就可以使用DLL。

只要您没有理由使用动态库,就可以使用静态库。

答案 9 :(得分:7)

Ulrich Drepper关于“How to Write Shared Libraries”的论文也是一个很好的资源,详细说明了如何最好地利用共享库,或者他称之为“动态共享对象”(DSO)。它更侧重于ELF二进制格式的共享库,但一些讨论也适用于Windows DLL。

答案 10 :(得分:5)

有关此主题的精彩讨论,请阅读Sun的this article

它具有所有好处,包括能够插入插入库。关于插入的更多细节可以在this article here中找到。

答案 11 :(得分:4)

实际上你正在进行的交易(在一个大型项目中)处于初始加载时间,库将会在某个时间进行链接,必须做出的决定是链接需要足够长的时间编译器需要咬紧牙关并预先做好,或者动态链接器可以在加载时执行它。

答案 12 :(得分:3)

如果库是静态的,那么在链接时,代码将与您的可执行文件链接。这使得您的可执行文件更大(比您进入动态路由时)。

如果库是动态的,那么在链接时,对可执行文件内置了对所需方法的引用。这意味着您必须运送可执行文件和动态库。您还应该考虑对库中代码的共享访问是否安全,首选加载地址以及其他内容。

如果您可以使用静态库,请使用静态库。

答案 13 :(得分:3)

如果您的库将在多个可执行文件之间共享,那么使其动态化以减小可执行文件的大小通常是有意义的。否则,一定要让它静止。

使用dll有几个缺点。加载和卸载它还有额外的开销。还有一个额外的依赖。如果您更改dll以使其与您的执行不兼容,它们将停止工作。另一方面,如果更改静态库,则使用旧版本的已编译可执行文件不会受到影响。

答案 14 :(得分:2)

如果您在嵌入式项目或专用平台上工作静态库是唯一的方法,那么很多时候它们在编译到您的应用程序时也不那么麻烦。同时拥有包含所有内容的项目和makefile可以让生活更美好。

答案 15 :(得分:2)

静态库是包含库的目标代码的归档,当链接到应用程序时,代码被编译到可执行文件中。共享库的不同之处在于它们不会编译到可执行文件中。而是动态链接器搜索一些目录,寻找它需要的库,然后将其加载到内存中。 多个可执行文件可以同时使用同一个共享库,从而减少内存使用量和可执行文件大小。但是,随后可以使用可执行文件分发更多文件。您需要确保将库安装到链接器可以找到它的某个使用系统上,静态链接可以消除此问题,但会产生更大的可执行文件。

答案 16 :(得分:2)

我们在项目中使用了很多DLL(> 100)。这些DLL彼此依赖,因此我们选择了动态链接的设置。但它有以下缺点:

  • 慢启动(&gt; 10秒)
  • DLL必须进行版本控制,因为Windows会根据名称的唯一性加载模块。自己编写的组件否则会得到错误的DLL版本(即已经加载的版本而不是自己的分布式集合)
  • 优化器只能在DLL边界内进行优化。例如,优化器尝试将经常使用的数据和代码放在一起,但这不能跨DLL边界工作

也许更好的设置是让一切成为一个静态库(因此你只有一个可执行文件)。这仅在没有发生代码重复时才有效。测试似乎支持这个假设,但我找不到正式的MSDN引用。例如,make 1 exe with:

  • exe使用shared_lib1,shared_lib2
  • shared_lib1使用shared_lib2
  • shared_lib2

shared_lib2的代码和变量只应出现在最终合并的可执行文件中一次。任何人都可以支持这个问题吗?

答案 17 :(得分:1)

我会给出一个一般的经验法则,如果你有一个大的代码库,所有代码库都建立在较低级别的库(例如,Utils或Gui框架)之上,你想要将它们分区为更易于管理的库,然后将它们设置为静态库。动态库并没有真正为你买任何东西而且意外就会减少 - 例如,只有一个单例实例。

如果您的库与代码库的其余部分完全分开(例如第三方库),那么请考虑将其设为dll。如果库是LGPL,则由于许可条件的原因,您可能还需要使用dll。