一、认识动静态库
- 静态库:程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库,在Linux中静态库是以(.a)为后缀;
- 动态库:程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。在Linux中静态库是以(.so)为后缀;
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码;
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking);
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间;
我们可以通过 [ ldd 可执行程序文件名 ]来查看可执行程序所依赖的库。
其中 /lib64/libc.so.6 就是可执行程序的库文件,它其实是一个软链接。我们可以通过以下命令来查看
[mlg@VM-20-8-centos lesson5-动静态库]$ ls /lib64/libc.so.6 -l
如何辨别它采用的是哪一种库呢?
1.我们可以通过后缀来区分(在Linux中)
在Linux中,以 .so 结尾的后缀,是动态库;以 .a 结尾的是静态库
在Windows中,以 .dll 结尾的后缀,是动态库;以 .lib 结尾的是静态库
库文件的名字:libxxx.so 和 libxxx.a
库的真实名字:去掉lib前缀,去掉 .a 、.so后缀,剩下的就是库的名称。
2.我们还可以通过file命令查看
上图中分别使用了动静态库对同一个文件进行编译的,通过file命名可以查看到所对应的链接信息,如果为安装静态库,是编译不通过的,具体安装教程可以点这里Linux——环境基础开发工具的使用
二、回顾编译链接的过程
在Linux中,gcc的编译可以分为一下四个步骤:
- 预处理:在预处理阶段主要负责的是头文件的展开、去掉注释、宏替换、条件编译等。以#号开头的是预处理指令:#define #if #include...... 此阶段产生【.i文件】
gcc -E mytest.c -o test.i
- 编译:此阶段完成语法和语义分析,然后生成中间代码,此中间代码是汇编代码,但是还不可执行,gcc编译的中间文件是[.s]文件。在此阶段会出现各种语法和语义错误,特别要小心未定义的行为,这往往是致命的错误。
gcc -S test.i -o test.s
- 汇编:此阶段主要完成将汇编代码翻译成机器码指令,并将这些指令打包形成可重定位的目标文件,[.o]文件,是二进制文件。此阶段由汇编器完成。
gcc -c test.s -o test.o
- 链接: 此阶段完成文件中叼用的各种函数跟静态库和动态库的连接,并将它们一起打包合并形成目标文件,即可执行文件。此阶段由链接器完成。
gcc test.o -o test
从以上四个阶段来看,我们要使用自己制作的库或者别人的库,一定是汇编完后产生的.o文件,我们只需要对这个.o文件进行链接就可以了;
三、库的制作和使用
库是一个二进制文件,想要使用库(给别人使用自己的制作的库或者使用别人的库)一定是由三个部分组成:库文件、头文件、文档说明;一般这个库文件就是函数的定义,头文件就是函数声明,我们只需要将这些打包好,别人使用我们头文件所给的接口就行。
//add.h #pragma once #include <stdio.h> extern int my_add(int x, int y);
//add.c #include "add.h" int my_add(int x, int y) { return x + y; }
//sub.h #pragma once #include <stdio.h> extern int my_sub(int x, int y);
//sub.c #include "sub.h" int my_sub(int x, int y) { return x - y; }
1.静态库的制作
1.生成二进制(.o)文件
首先将上面的.c文件生成.o文件
2.打包
我们将生成好的.o文件进行打包:
[mlg@VM-20-8-centos test_lib]$ ar -rc libmymath.a add.o sub.o
ar命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar命令的-r 选项和-c选项进行打包。
- -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
-
r(rreate):建立静态库文件。
我们可以用 ar 命令的 -t 选项和 -v 选项查看静态库当中的文件。
- -t:列出静态库中的文件。
- -v:显示详细的信息
3.发布静态库
静态库要发布出去供别人使用,只要库文件(所有的.o文件)是不够的,我们需要将其和头文件一起发布出去,别人只要看到头文件,就大致了解如何使用了
当然,我们也可以直接编写Makefile,就不需要我们一步一步的完成静态库的打包及发布
2.静态库的使用
方法一
上文中,我们已经有了静态库output了,别人该如何使用呢? 例如:想要在friend文件下使用这个库:
现在在friend目录下有一个mytest.c文件和一个静态库文件lib,mytest.c想要使用lib,我们先编写一下mytest.c代码:
#include "add.h" #include "sub.h" int main() { int x = 30; int y = 20; int ret1 = my_add(x, y); int ret2 = my_sub(x, y); printf("ret1 = %d\n",ret1); printf("ret2 = %d\n",ret2); return 0; }
进行编译:
编译后,报警告:没有头文件,可是明明在lib文件下有我们要的有文件以及库文件,这是为什么呢?
其实,编译器在编译的时候,会在当前的目录的文件中去找,不会去当前目录的文件夹中去找,lib目录下的头文件以及库文件,和mytest.c不是同级目录,所以编译会出错;
我们在编译的时候就需要告诉编译器,需要的头文件在哪个目录下。
[mlg@VM-20-8-centos friend]$ gcc mytest.c -I ./lib
此时,又有警告了:链接错误,未定义的两个函数?在lib目录下已经定义了两个函数,并且打包好了?为什么还是报错呢?
其原因和上面一样;所以我门还需要告诉编译器库文件在lib目录下:
[mlg@VM-20-8-centos friend]$ gcc mytest.c -I ./lib -L ./lib
头文件和库文件所在位置都告诉编译器了,怎么还是报错呢?
其实,头文件和库文件都在lib目录下,在mytest.c文件中,是明确的包含了,add.h和sub.h的,gcc在编译的时候能够认识,但不认识库文件,如果在lib目录下有多个库文件,gcc是不知道你想要使用哪个库的。所以我们还需要指明库的名字。
[mlg@VM-20-8-centos friend]$ gcc mytest.c -I ./lib -L ./lib -l mymath
此时,整个程序才能够正确编译并运行
总结:
我们在使用静态库进行编译链接时,需要指定头文件的所在路径,库文件的所在路径以及所要掉用的库名称
-I
:指定头文件所在路径。-L
:指定库文件所在路径。-l
:指明需要链接库文件路径下的哪一个库
[mlg@VM-20-8-centos friend]$ gcc mytest.c -I ./lib -L ./lib -l mymath
同样,我们也可以编写Makefile:
方法二
对比我们之前在编译某个.c文件时,为什么有加上这些选项呢?。这是因为之前的库都是在系统的默认路径下,所以我们可以将我们做好的静态库拷贝到系统的默认路径下,也是可以达到不需要加这些选项的效果;但是严重不推荐。
3.动态库的制作
编写如下四个文件:其中源文件包含add.c和sub.c,头文件包含add.h和sub.h;用来制作动态库并打包
//add.h #pragma once #include <stdio.h> extern int my_add(int x, int y);
//add.c #include "add.h" int my_add(int x, int y) { return x + y; }
//add.c #include "add.h" int my_add(int x, int y) { return x + y; }
//sub.c #include "sub.h" int my_sub(int x, int y) { return x - y; }
1.生成二进制(.o)文件
首先将上面的.c文件生成.o文件
[mlg@VM-20-8-centos test_lib]$ gcc -fPIC -c add.c -o add.o [mlg@VM-20-8-centos test_lib]$ gcc -fPIC -c sub.c -o sub.o
-fPIC:作用是告知编译器 生成位置无关代码(编译产生的代码没有绝对位置,只有相对位置);从而可以在任意地方调用生成的动态库。
2.打包
我们将生成好的.o文件进行打包:
[mlg@VM-20-8-centos test_lib]$ gcc -shared -o libmymath.so add.o sub.o
-shared:linux在gcc编译时加上 -shared 参数时,目的是使源码编译成动态库 .so 文件;
3.发布动态库
将库文件和所有的头文件组织起来,放到lib目录下,这样就可以发布动态库了
当然,我们也可以直接编写Makefile,就不需要我们一步一步的完成静态库的打包及发布
4.动态库的使用
动态库的使用大致和静态库类似,但略有区别。我们先使用静态库的方法来实现动态库的链接。
能够成功编译,但是运行却报错了,为什么呢?
我们通过ldd命令列出动态库依赖关系,发现是not found。虽然已经告诉了编译器库文件和头文件的路径所在位置,但是当编译器编译好后,就与编译器无关了;当我们执行(运行)可执行程序a.out时,是由加载器来完成的。所以我们需要在运行时,告诉系统库文件在哪里;
方法一
拷贝到系统的默认路径下,一般指/usr/lib 这里不做演示,严重不推荐;
方法二
更改 LD_LIBRARY_PATH
[mlg@VM-20-8-centos lib]$ export LD_LIBRARY_PATH=/home/mlg/lesson5-动静态库/friend/lib
LD_LIBRARY_PATH环境变量用于在程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径.注意,LD_LIBRARY_PATH中指定的路径会在系统默认路径之前进行查找;
添加好后,我们再次查看,发现路径已经指定好了
再次编译运行:
四、动态库与静态库特点总结
静态库的特点:
静态库在可执行程序链接时就加入到可执行代码中,在物理上成为可执行程序的一部分;程序运行时将不再需要该静态库。
相对于动态库链接生成的程序,静态函相当于编译器将代码补充完整了,因此执行程序会大一些,但是运行起来相对快些;
静态库是牺牲了空间效率,换取了时间效率;
动态库的特点:
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在;
动态库只有在程序执行时, 那些需要的函数代码才被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间;
由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些;
动态库是牺牲了时间效率,换取了空间效率;