Static Libraries (.lib, .a) and Shared Library (.dll, .so)
1. 静态链接和动态链接
源代码生成可执行文件,一般可以分解为四个步骤:预处理 (prepressing)、编译 (compilation)、汇编 (assembly) 和链接 (linking)。预处理过程主要处理源代码中以 #
开始的预编译指令。编译过程把预处理完成的文件进行词法、语法、语义等分析并产生相应的汇编代码文件。汇编过程将汇编代码文件翻译成机器可以执行的目标文件。链接过程将汇编生成的目标文件集合相链接并生成最终的可执行文件。
链接器在链接静态链接库的时候是以目标文件 .o
为单位的。在链接一个静态库时,如果该静态库里的某些方法没有任何地方调用,则这些没有被调用到的方法或变量将会被丢弃掉,不会被链接到目标程序中。这样可以大大减小生成二进制文件的体积。
动态链接在程序运行时才将它们链接在一起形成一个完整的程序,不像静态链接一样把所有程序模块都链接成一个单独的可执行文件。此时在整个程序的链接过程中,链接器只是拷贝了一些重定位和符号信息。在程序加载时才会解析 .so
文件中代码和数据的引用。程序在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,在内存里是否已有此库函数的拷贝。如果有,则让其共享那一个拷贝,只有没有才链接载入。在程序运行的时候,被调用的动态链接库函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须使用相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器,这些对象文件是用来做动态链接库的,所以要用地址无关代码 (Position Independent Code,PIC)。
动态链接库的加载方式有两种:隐式加载和显示加载。
Linux 下进行链接的缺省操作是首先链接动态库。如果同时存在静态和动态库,不特别指定的话,将与动态库相链接。
2. 库的搜索路径
库的搜索路径包括编译时的搜索 .a
和 .so
和运行时的搜索 .so
。使用 -L
可以链接 .so
编译程序,但是运行时并不会从 -L
指定的目录搜索从而导致可能运行时找不到文件。
.a
是静态函数库。当同时运行多个程序并且都使用到同一个函数库的函数时,内存中就会有同一函数的多份副本,会消耗大量宝贵的内存。
.so
是共享函数库,可以克服 .a
函数库的不足。当一个程序使用共享函数库时,程序本身不再包含函数代码,而只是引用共享代码。实际调用时,共享库才被加载到内存中。
.a
文件类似于 Windows 下的 .lib
文件,.so
文件类似于 Windows 下面的 .dll
文件。
3. 静态链接和动态链接
- 静态链接时,静态库的所有执行代码被直接编译到目标程序中。
- 动态链接时,仅仅把动态库的函数和变量的符号名,地址偏移量等导入到目标程序。只有在目标程序运行的时候才把动态库的执行代码加载到内存中。
- 动态链接的项目容易管理,把不同模块封装成不同的动态库,如果模块功能修改,一般只需要重新生成该动态库,不用重新编译其他模块和目标程序。
- 静态链接的程序修改任何一个地方都必须重新编译整个程序。
- 静态链接生成的目标程序体积比动态链接的大,但是加载速度更快,发布更容易,不需要检查发布机器上是否有该动态库或者动态库版本是否符合要求。
- 如果多个程序使用一个动态库,则该库的执行代码只会在内存中加载一次。静态库是多次加载 (静态库链接完就没用了,等于目标程序的一部分)。
- 从调试的角度来说,静态连接的程序调试方法和独立程序没有任何区别,而动态库的调试相对要复杂一些,因为库里面的符号地址都是相对地址。
- 如果同时存在静态库和动态库时,优先选择动态库。
GCC 静态链接时,直接把静态库的名字放在 gcc / g++
后面。GCC 动态连接时,使用 -l
指定库,-L
指定库的路径,注意动态库名必须是 lib
开头,后缀名为.so
。如果目录中同时存在 2 种库,gcc / g++
优先选择动态库。链接指令中既要链接动态库又要链接静态库,可以用 -Wl,-dn
和 -Wl,-dy
参数选项来切换。