静态库与动态库的分析与简单制作
1、什么是库?
库(library)是一种可执行代码的二进制形式,通常把一些常用的函数制作成各种函数库,然后被系统载入内存中运行。库内一般都是各种标准程序、子程序、相关文件以及目录等的集合,内置一些经常用的程序。主要有:
1)标准子程序:例如三角函数、反三角函数等
2)标准程序:例如解常微分方程等
3)服务性程序:例如输入、输出、磁盘操作、调试等。
由于windows与linux系统不同,因此二者的二进制库是不兼容的。Linux系统下的库分为静态库与动态库两种。二者的不同点是在载入时间的不同。
静态库在程序编译时的链接阶段被链接到目标代码中,运行程序时将不再需要静态库。编译后的可执行程序体积较大。
动态库在程序编译时并不会马上链接到目标代码中,而是在执行阶段才被程序载入,因此编译后的可执行程序体积较小,但是需要系统动态库存在。
库是前辈高手写好的成熟的可以直接复用的代码,只需遵守使用协议即可。不可能所有人的编程学习都是从零开始,库的存在使得程序开发变得更加简易。而且如果不同的应用程序调用同样的库,那么内存内只需有一份该库的实例即可,节省了存储空间。
2、静态库、动态库比较
静态库 | 动态库 | |
---|---|---|
常见文件名 | libxxx.a | libxxx.so |
载入时间 | 编译时(链接阶段) | 程序运行时 |
编译后文件大小 | 与程序整合到一起,程序体积大 | 没有与程序整合,程序体积小 |
运行时是否需要 | 不再需要(无依赖) | 需要(有依赖) |
更新 | 不方便,库更新需要重新编译 | 方便无需重新编译 |
资源共享 | 进程间无法资源共享 | 可以实现进程间的资源共享 |
3、制作一个静态库
我们可以使用GNU下的ar工具来制作一个静态库
ar是类似gcc的一个GNU工具包内的工具,作用是建立、修改、提取归档文件。归档文件是包含多个文件内容的一个大文件,被包含文件的原始内容、权限、时间戳、所有者等属性都保存于归档文件中,并且可以通过“提取”来还原该文件。
示例:自己制作一个静态库,库函数的功能是传递一个字符串并输出。
第一步:需要准备3个文件:func.h、func.cc、test.c。其中hello.h和hello.c用于制作静态库,test.c是测试程序主函数
//文件fnc.h
#ifndef __FUNC_H__
#define __FUNC_H__
#include<stdio.h>
void func(int, int);
#endif
//文件func.c
#include"func.h"
void func(int a, int b) {
printf("sum = %d\n", a + b);
}
//文件test.c
#include <stdio.h>
#include"func.h"
int main() {
func(3, 4);
return 0;
}
第二步:将hello.c编译生成目标文件hello.o
gcc func.c -c func.o
第三步:使用ar将hello.o制作成静态库
ar crs libmyfunc.a hello.o
ar参数解析:
1.c:表示无提示方式创建文件包
2.r:在文件包中替代文件
3.s:强制重新生成文件包的符号表
这样就制作了一个名为libmyfunc.a的文件包(即静态库)
第四步:编译test.c,将刚制作的静态库加载至程序内
gcc test.c -L. -lmyfunc -o test
gcc参数解析:
1.-L:表示增加目录,让编译器可以在该目录下寻找库文件。后面的 . 表示当前目录;
2.-l:表示加载libXXX.a/libXXX.so库文件。
这样我们就生成了一个test的可执行文件。执行该文件,会输出"sum = 7",这样我们就成功制作了一个库函数test。
若我们删除库(即libmyfunc.a文件),再次执行该程序仍然可以得到正确的结果。这是因为静态库在链接阶段已经和程序整合到一起,即使原始库文件不存在,程序依然可以成功执行。
4、制作一个动态库
我们可以使用gcc工具来制作一个动态库
示例:自己制作一个动态库,库函数的功能是求和并输出。
第一步:需要准备3个文件:func.h、func.c、test.c。其中func.h和func.c用于制作动态库,test.c是测试程序主函数
第二步:使用gcc编译生成动态库
gcc func.c -fPIC -c -o func.o
gcc func.o -shared -o libmyfunc.so
(或者直接一步:gcc func.c -fPIC -shared -o libmyfunc.so)
gcc参数解析:
1.-fPIC(或-fpic):表示编译为位置独立的代码。位置独立的代码即位置无关代码,在可执行程序加载的时候可以存放在内存内的任何位置。若不使用该选项则编译后的代码是位置相关的代码,在可执行程序加载时是通过代码拷贝的方式来满足不同的进程的需要,没有实现真正意义上的位置共享。
2.-shared:指定生成动态链接库
此时我们就生成了动态库libmyfunc.so。
若此时编译文件时加载库
gcc test.c -L. -lmyfunc -o test
运行文件hello时会发现报错:
./test: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
这是因为Linux系统还无法定位到我们自己制作的库的位置,即我们暂时还无法使用该动态库。
对于Linux系统而言,在可执行程序加载动态库的时候,不仅要知道该库的名字,还需要知道其绝对路径。
我们可以使用ldd指令查看某个可执行程序加载库的情况
ldd hello
linux-gate.so.1 => (0xb77b0000)
libmyhello.so => not found
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75f6000)
/lib/ld-linux.so.2 (0xb77b1000)
第三步:定位自己制作的动态库
要想让自己制作的动态库生效,我们需要了解正常情况下系统是如何加载一个动态库的。以我们熟悉的stdio库为例,系统在加载标准输入输出库时遵循以下几个步骤:
1.执行./test指令,终端解释该指令,终端指示应加载动态库stdio,寻找存放动态库的配置文件。
2.存放动态库的配置文件默认目录为/etc/ld.so.conf.d/以及下属的众多子目录内的配置文件。配置文件指示该库的绝对路径在/usr/lib或/lib下。
3.去往/usr/lib或/lib,将存储的stdio库加载到程序test中。
为了让系统能成功找到自己制作的动态库,需要定位到该动态库的位置。参照正常加载动态库的方式,我们可以有三种方式:
1)把自己制作的库拷贝到/usr/lib和/lib下。
2)在LD_LIBRARY_PATH环境变量中添加自己制作的库所在的位置。
3)添加/etc/ld.so.conf.d/XXX.conf文件(XXX需要自己命名),把库所在的路径添加到文件末尾并执行ldconfig刷新。
注意:练习这三种方法时尽量清除上一种方法的效果影响保证三种方法是独立生效的。
第一种:将库拷贝到/usr/lib和/lib下。
sudo cp libmyfunc.so /usr/lib
sudo cp libmyfunc.so /lib
此时再执行./hello即可得到正确的显示结果。
第二种:修改LD_LIBRARY_PATH环境变量
sudo vim /etc/bash.bashrc
在文件最后,添加:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/linux/file/test
(路径为动态库文件所在的路径)保存退出,重启终端,此时再执行./test即可得到正确的显示结果。
第三种:添加/etc/ld.so.conf.d/XXX.conf文件
sudo vim /etc/ld.so.conf.d/my.conf
在文件内添加动态库的目录
/home/linux/file/test
保存退出,执行ldconfig使设置生效
sudo ldconfig
此时再执行./test即可得到正确的显示结果。
思考:使用静态库与动态库时,加载库的指令是相同的(都是gcc test.c -L. -lmyfunc -o test)。若静态库与动态库重名(例如libmyfunc.a和libmyfunc.so),则系统会以哪个库为准?
练习:验证静态库与动态库重名时的加载库的情况。
提示:使用同样的代码分别编译成静态库与动态库,两个库的名称相同,但是不配置动态库的路径。然后编译生成可执行程序,若该程序可以执行则表示以静态库为准,若无法执行则表示以动态库为准。
从程序hello的执行结果可以看出,当静态库与动态库重名时,系统会以动态库为准。可以动态静态库重名,然后编译后执行./test,不定位自己的动态库,还是报错./test: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory,说明系统会以动态库为准。