1.动静态库的基本原理
动静态库的本质是可执行程序的 “半成品”
一个C程序编译形成可执行程序需要经过四个步骤:
- 预处理: 头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
- 编译: 词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
- 汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
- 链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。
①多个文件经过编译形成.o文件,最后链接形成可执行程序
②如果其他的项目也需要test的4个.o文件,也可以进行链接形成可执行程序
③对于可能频繁用到的源文件,比如这里的test1.c、test2.c、test3.c、test4.c,我们可以将它们的目标文件test1.o、test2.o、test3.o、test4.o进行打包,之后需要用到这四个目标文件时就可以直接链接这个包当中的目标文件了,而这个包实际上就可以称之为一个库。
所有的库本质是:一堆.o的集合,不包含main,但是包含了大量的方法可被调用!
2.认识动静态库
①代码+结果 ; 静态库生成的可执行程序的文件大小明显大于动态库。
②使用动静态库编译形成可执行程序
③在Linux下,使用 ldd 文件名 来查看一个可执行程序所依赖的库文件
libc.so.6 就是该可执行程序所依赖的库文件,我们通过ls命令可以发现libc.so.6实际上只是一个软链接 , 链接的文件在同一目录下面
④查看动静态链接生成的可执行程序的文件类型时,它们分别是动态链接和静态链接的。
- 在Linux当中,以.so为后缀的是动态库,以.a为后缀的是静态库。
- 在Windows当中,以.dll为后缀的是动态库,以.lib为后缀的是静态库。
可执行程序所依赖的libc.so.6实际上就是C动态库,当我们去掉一个动静态库的前缀lib,再去掉后缀.so或者.a及其后面的版本号,剩下的就是这个库的名字
3.动静态库各自的特征
①静态库
静态库是程序在编译链接的时候把库的代码复制到可执行文件当中的,生成的可执行程序在运行的时候将不再需要静态库,因此使用静态库生成的可执行程序的大小一般比较大。
- 优点 : 使用静态库生成可执行程序后,该可执行程序就可以独自运行,不再需要库了
- 缺点 : 占空间(磁盘 + 内存) ;可执行程序放在磁盘上,运行时加载到内存。
静态链接浪费空间: ①静态链接后形成的克执行程序比较大
②多个C静态程序加载的时候,使用相同库,一定会有在内存中存在大量的重复代码!
②动态库
- 动态库是程序在运行的时候才去链接相应的动态库代码的,多个程序共享使用库的代码。
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
- 在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接。
- 动态库在多个程序间共享,节省了磁盘空间,操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
- 优点 : 节省磁盘和内存空间,且多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存当中不会存在重复代码。
- 缺点 : 必须依赖动态库,否则无法运行
4.静态库的打包与使用
(1)测试使用的 .h 和 .c文件
(2)开始打包
①源文件生成对应的目标文件
②使用ar命令将所有目标文件打包为静态库
ar
命令是gnu的归档工具,常用于将目标文件打包为静态库。
- -r (replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
-c
(create):建立静态库文件。
此外,我们可以用ar
命令的-t
选项和-v
选项查看静态库当中的文件。
-t
:列出静态库中的文件。-v
(verbose):显示详细的信息。
③将头文件可库文件打包成一个文件
- 当我们把自己的库给别人用的时候,实际上需要给别人两个文件夹,一个文件夹下面放的是一堆头文件的集合,另一个文件夹下面放的是所有的库文件。
- 因此,在这里我们可以将add.h和sub.h这两个头文件放到一个名为include的目录下,将生成的静态库文件libmycal.a放到一个名为lib的目录下,然后将这两个目录都放到mymathlib下,此时就可以将mathlib给别人使用了。
④使用Makefile一键打包
(3)使用
①方法一 : 编译时带选项
此时使用gcc编译main.c生成可执行程序时需要携带三个选项:
-I
:指定头文件搜索路径。-L
:指定库文件搜索路径。-l
:指明需要链接库文件路径下的哪一个库(lib目录下面可能有很多 .a 的库)。
注意:
- 因为编译器不知道你所包含的头文件add.h在哪里,所以需要指定头文件的搜索路径。
- 因为头文件add.h当中只有add函数的声明,并没有该函数的定义,所以还需要指定所要链接库文件的搜索路径。
- 实际中,在库文件的lib目录下可能会有大量的库文件,因此我们需要指明需要链接库文件路径下的哪一个库。库文件名去掉前缀lib,再去掉后缀.so或者.a及其后面的版本号,剩下的就是这个库的名字。
- -I,-L,-l 这三个选项后面可以加空格,也可以不加空格
②加入系统默认搜索路径(不推荐)
- 不用指明库路径和头文件路径,因为已经在系统默认目录了,但是需要把库名字告诉编译器,为什么C语言就不需要,因为gcc编译器知道你是C语言默认就找到C库了,但是此时你要链接哪一个库它还是不知道,需要指明。
- 实际上我们拷贝头文件和库文件到系统路径下的过程,就是安装库的过程。但并不推荐将自己写的头文件和库文件拷贝到系统路径下,这样做会对系统文件造成污染 , 时间长了不删除自己就忘记了。
5.动态库的打包与使用
(1)打包
①让所有源文件生成对应的目标文件
需要携带-fPIC
选项:
-fPIC
(position independent code):产生位置无关码
关于fPIC :
- -fPIC作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
- 如果不加-fPIC选项,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的拷贝,并且每个拷贝都不一样,取决于这个.so文件代码段和数据段内存映射的位置。
- 不加-fPIC编译出来的.so是要在加载时根据加载到的位置再次重定位的,因为它里面的代码BBS位置无关代码。如果该.so文件被多个应用程序共同使用,那么它们必须每个程序维护一份.so的代码副本(因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。
- 我们总是用-fPIC来生成.so,但从来不用-fPIC来生成.a。但是.so一样可以不用-fPIC选项进行编译,只是这样的.so必须要在加载到用户程序的地址空间时重定向所有表目
②将.o目标文件打包成动态库
③将头文件和生成的动态库组织起来进行打包
与生成静态库时一样,为了方便别人使用,在这里我们可以将 add.h和sub.h 这两个头文件放到一个名为include的目录下,将生成的动态库文件 libcal.so 放到一个名为lib的目录下,然后将这两个目录都放到mylib下,此时就可以将mylib给别人使用了。
④使用Makefile
(2)使用
①使用该动态库的方法与使用静态库的方法一样,我们既可以使用 -I,-L,-l这三个选项来生成可执行程序,也可以先将头文件和库文件拷贝到系统目录下,然后仅使用-l选项指明需要链接的库名字来生成可执行程序
使用gcc编译test.c生成可执行程序时,需要用-I选项指定头文件搜索路径,用-L选项指定库文件搜索路径,最后用-l选项指明需要链接库文件路径下的哪一个库
②运行时报错解释 : 我们使用-I
,-L
,-l
这三个选项都是在编译期间告诉编译器我们使用的头文件和库文件在哪里以及是谁,但是当生成的可执行程序生成后就与编译器没有关系了,此后该可执行程序运行起来后,OS找不到该可执行程序所依赖的动态库.
③解决OS找不到动态库方法
1)拷贝.so文件到系统共享库路径下
OS找不到我们自己的动态库,就把它放到OS默认存放动态库的文件中。
2)更改 LD_LIBRARY_PATH 环境变量
LD_LIBRARY_PATH 是程序运行时动态查找库时所要搜索的路径,我们只需将动态库所在的目录路径添加到 LD_LIBRARY_PATH 环境变量当中即可
3)配置 /etc/ld.so.conf.d/
/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件了。