博文说明【前言】:


    本文将通过个人口吻介绍Linux中静态链接库和动态链接库相关知识,在目前时间点【2017年6月14号】下,所掌握的技术水平有限,可能会存在不少知识理解不够深入或全面,望大家指出问题共同交流,在后续工作及学习中如发现本文内容与实际情况有所偏差,将会完善该博文内容。


本文参考文献引用链接:

1、http://developer.51cto.com/art/201107/275783.htm【动态链接库】
2、http://www.jianshu.com/p/8743a0edb1ee【好文,讲解动态链接库】
3、http://c.biancheng.net/cpp/html/2750.html【好文,讲解动态链接库】
4、http://www.cnblogs.com/ardar/articles/357321.html【好文,推荐】
5、http://www.cnblogs.com/catch/p/3857964.html【linux下动态链接实现原理】



正文:


写在前面:静态/动态链接库都是库函数的应用,也就是说他们都是库函数。

通常情况下,对函数库的链接是放在编译时期(compile time)完成的。

在使用静态库的情况下, 编译链接可执行文件时,链接器把所有相关的对象文件 (object file)与牵涉到的函数库(library)和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件)。在运行时,程序与函数库再无瓜葛,因为所有需要的函数已经拷贝进来了下。所以这些函数库被成为静态库(static libaray),换句话说,函数和过程的代码就在程序的可执行文件(exe)中,该文件包含了运行时所需的全部代码,当多个程序都调用相同的函数时,内存中就会存在这个函数的多个拷贝。在Linux中,通常文件名为“libxxx.a”的形式,Windows通常扩展名为.LIB。

当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。因此,如果有多个程序调用这些函数库,那么在内存中就会存在多个拷贝。


其实,我们也可以将对这些库函数的链接载入推迟到程序运行的时期(runtime)再执行。这就是如雷贯耳的动态链接库(dynamic link library)技术,就是说在程序运行过程中,使用到某个函数时我再调用这些函数,而不是一开始就将所有函数都加载进程序,也就是说动态链接所调用的喊出代码没有被拷贝到程序的可执行文件中去,如果有多个程序调用同一个动态链接库,系统内存中只会有一份数据,在Linux中,通常文件名为“libxxx.so”的形式。

因此目前实际情况是许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当程序真正运行的时候,相应的DLL文件才会被调用。一个应用程序可有多个DLL 文件,同时一个DLL文件也可能被几个应用程序所共用,这样的DLL文件被称为共享DLL文件。

注意:DLL也是一个被编译过的二进制程序,可以被其他程序调用,但与exe不同,DLL不能独立运行必须由其他程序调用载入内存。DLL中封装了很多函数,只要知道函数的入口地址,就可以被其他程序调用。

ps:通过使用 DLL,程序可以实现模块化,程序可以由相对独立的模块组件组成。因为模块是彼此独立的,程序在启动时不用加载所有的模块,因以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。

【示例】:
例如,在 Windows 操作系统中,Comdlg32 DLL执行与对话框有关的常见函数。因此,每个程序都可以使用调用DLL中包含的功能来实现“打开”对话框。这有助于促进代码重用和内存的有效使用。


一、【静态链接库】


1、类库的名字一般是libxxx.a
2、代码的装载速度快,执行速度也比较快,因为编译时它只会把你需要的那部分链接进去。
3、应用程序相对比较大,如果多个应用程序使用的话,会被装载多次,浪费内存。
4、如果静态函数库改变了,那么你的程序必须重新编译。
5、如果你的系统上有多个应用程序都使用该库的话,建议把它编译成动态库,这样虽然刚启动的时候加载比较慢,但是多任务的时候会比较节省内存;如果你的系统上只有一到两个应用使用该库,并且使用的API比较少的话,就编译成静态库吧,一般的静态库还可以进行裁剪编译,这样应用程序可能会比较大,但是启动的速度会大大提高。


二、【动态链接库】


1、类库的名字一般是 libxxx.so
2、共享:多个应用程序可以使用同一个动态库,启动多个应用程序的时候,只需要将动态库加载到内存一次即可;
3、开发模块好:要求设计者对功能划分的比较好。

4、动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。


三、【静态链接库和动态链接库的区别】:


1、简单的说,静态库和应用程序编译在一起,在任何情况下都能运行,而动态库是动态链接,顾名思义就是在应用程序启动的时候才会链接,所以,当用户的系统上没有该动态库时,应用程序就会运行失败。

2、如果程序是在编译时加载库文件的,就是使用了静态库。如果是在运行时加载目标代码,就成为动态库。换句话说,如果是使用静态库,则静态库代码在编译时就拷贝到了程序的代码段,程序的体积会膨胀。如果使用动态库,则程序中只保留库文件的名字和函数名,在运行时去查找库文件和函数体,程序的体积基本变化不大。

3、

静态库的原则是“以空间换时间”,增加程序体积,减少运行时间;

动态库则是“以时间换空间”,增加了运行时间,但减少了程序本身的体积。

4、【案例演示,生成动态链接库文件】:


文件目录树如下:


libtest/
|-- myjob.c
|-- myjob.h
|-- test.c

【静态库部分】

A.做成静态库 libmyjob.a

$ gcc -c myjob.c -o myjob.o 
$ ar -c -r -s libmyjob.a myjob.o

B.链接

$ gcc test.o libmyjob.a -o test

C.引用库情况(无所要信息)

$ ldd test 
linux-gate.so.1 => (0xffffe000) 
libc.so.6 => /lib/libc.so.6 (0xb7e29000) 
/lib/ld-linux.so.2 (0xb7f6e000)

【动态库部分】

A.做成动态库libmyjob.so

$ gcc -Wall –fPIC -c myjob.c -o myjob.o   

$ gcc -shared -o libmyjob.so myjob.o


-shared: 该选项指定生成动态连接库(让连接器生成T >类型的导出符号表,有时候也生成弱连接 W >类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC: 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动>>态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的>。
-L.: 表示要连接的库在当前目录中。
LD_LIBRARY_PATH: 这个环境变量指示动态连接器可以装载动态库的路径。

B.链接
链接方法一、拷贝到系统库里再链接,让 gcc 自己查找:

$ cp libmyjob.so /usr/lib 
$ gcc -o test test.o -lmyjob
这里我们可以看到了 -lmyjob 选项, -l[lib_name] 指定库名,他会主动搜索。 lib[lib_name].so 这个搜索的路径可以通过 gcc --print-search-dirs 来查找。

链接方法二 ,手动指定库路径

$  gcc -o test test.o -lmyjob -B /path/to/lib
-B 选项就添加 /path/to/lib 到 gcc 搜索的路径之中。这样链接没有问题但是方法 II 中手动链接好的程序在 执行 时候仍旧需要指定库路径( 链接和执行是分开的 )。需要添加系统变量 LD_LIBRARY_PATH :

   1. $ export LD_LIBRARY_PATH=/path/to/lib 
这个时候再来检测一下test 程序的库链接状况 ( 方法 I 情况 )
   1. $ ldd test 
   2. linux-gate.so.1 => (0xffffe000) 
   3. libmyjob.so => /usr/lib/ libmyjob .so (0xb7f58000) 
   4. libc.so.6 => /lib/libc.so.6 (0xb7e28000) 
   5. /lib/ld-linux.so.2 (0xb7f6f000)
   6. 
是不是比静态链接的程序多了一个 libmyjob.so? 这就是静态与动态的最大区别。

静态情况下,它把库直接加载到程序里,而在动态链接的时候,它只是保留接口,将动态库与程序代码独立。这样就可以提高代码的可复用度,和降低程序的耦合度。
另外,运行时,要保证主程序能找到动态库,所以动态库一般发布到系统目录中,要么就在跟主程序相对很固定的路径里,这样不管主程序在本机何时何地跑,都能找得到动态库。而静态库只作用于链接时,运行主程序后静态库文件没存在意义了。






结尾:



     感谢阅读,祝有收获的一天,谢谢!