Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。
本篇导航
0. 静态库与动态库
先来总体描述下静态库与动态库的区别.
静态库是将头文件总体复制到可执行文件当中。动态库是在可执行程序运行时进行了动态链接(所需要某个实现方法就去内存中查找).
所以静态链接所形成的可执行文件可以在没有相关配置的设备上运行,而动态链接的可执行程序对设备环境要求较高.
通常情况下,我们将自己的代码提供给别人使用时,往往只会提供: 头文件与源码打包成的库.这个库可以是动态链接也可以是静态链接.
- 总结:动静态库都为将形成的二进制码进行链接的过程,二者区别仅为链接方式不同
1. 制作与使用静态库
接下来介绍如何制作一个自己的静态链接库.
我们先正常进行一段编码.分别编写头文件.h与方法.c
// .h编写#pragmaonceintadd(int x,int y);
// .c编写externint myerrno;intadd(int x,int y){return x+y;}
我们先使用如下指令对.c进行编译
gcc -c myadd.c
# 该指令会自动生成.c同名的.o文件
接下里就是对myadd.o进行链接的过程
我们使用ar工具带上rc选项来进行链接,具体指令如下
静态链接库的文件名格式为 libxxx.a
ar -rc libmyadd.a myadd.o
此时就编译完成了我们所需要的静态库.
现在我们试试写一段代码去引用下我们的方法.
#include"myadd.h"#include<stdio.h>intmain(){printf("%d\n",add(1,2));return0;}
接下来我们试着用gcc进行编译运行看看
gcc -otest test.c
显示其不认识我们的add函数,但是我们不是将头文件包含了嘛?
这是因为gcc编译时会去特定的路径下查找头文件与库文件信息.
该目录结构一般如下
在lib/include路径下存放我们自定义的头文件 在lib/myaddlib存放我们打包生成好的库文件.
之后,手动指定gcc去哪里寻找对应的链接文件
gcc -otest test.c -I lib/include -L lib/myaddlib -lmyadd
其中 -I表示头文件在哪个路径下 -L表示链接文件在哪个路径下 -l表示具体是哪个链接文件的路径(此时要去掉前缀lib去掉后缀.a)
为什么头文件不需要指定名称了呢?因为我们在代码中已经手动include "头文件"了
那么为什么我们平常使用时没有这么麻烦呢?
因为系统在安装库时,把所有需要的文件放在了指定目录下,gcc编译时会自己去该目录进行寻找,我们仅需要制定形成的链接文件即可
头文件存储在 /usr/include中 形成的链接文件放在/lib64下
我们试试将我们的东西软连接到对应目录下
sudoln-s$(pwd)/lib/include /usr/include/lib
sudoln-s$(pwd)/lib/myaddlib/libmyadd.c /lib64/libmyadd.c
此时我们直接带上-l选项即可完成编译
gcc -otest test.c -lmyadd
这也为安装静态库的方法,我们可以通过复制/软链接到对应的目录
在编译完成后该文件即可独立运行,此时将编译时所依赖文件全部删除也不影响该程序运行
makefile:
lib=libmyadd.a
$(lib):myadd.o
ar -rc $@ $^
myadd.o:myadd.c
gcc -c $^
.PHONY:clean
clean:
rm -rf *.o lib *.a test *.a*
.PHONY:output
output:
mkdir -p ./lib/include
mkdir -p ./lib/myaddlib/
cp *h ./lib/include
cp *a ./lib/myaddlib
2. 制作与使用动态库
先来介绍一个指令: ldd + 可执行文件
可以列出当前文件运行时所需的动态库目录,例如我们刚刚使用的test
为了方便介绍,我们将多个方法.c文件打包成一个动态库文件
// myPrintf.h#pragmaonce#include<stdio.h>voidPrintf();
// myLog.h#pragmaonce#include<stdio.h>voidLog();
// myPrintf.cvoidPrintf(){printf("hello myprintf\n");}
// myLog.cvoidLog(){printf("hello mylog\n");}
将其进行编译成二进制文件
gcc -fpic-c myPrintf.c myLog.c
fpic为与位置无关码
将其进行链接
gcc -shared-o libmyfunc.so myPrintf.o myLog.o
动态库的后缀为so,前缀为lib
此时一个名为 libmyfunc.so的可执行文件就被编译生成了.(至于为什么是可执行的我们之后再说)
为了规范 我们将其放进如下目录
同样编写一段代码尝试使用
// test.c#include"./lib/include/myLog.h"#include"./lib/include/myPrintf.h"intmain(){Printf();Log();return0;}
编译指令与静态库相似
gcc -o mytest test.c -I ./lib/include -L ./lib/lib -lmyfunc
但是,此时我们直接运行会发现.
原因:动态库并不会将自己的库文件放进可执行程序,其运行时动态在内存中进行查找.而我们上面仅告诉了编译器我们的动态库在哪,而加载器并不知道,所以找不到
- 方法也是将自己的动态库文件拷贝/软连接到/lib下
- 一个方法是修改/etc/ld.so.conf.d下的文件内容
在该目录下创建一个.conf结尾的文件,将动态库所在的文件目录路径填入即可
sudo ldconfig
- 还有一个方法是导入环境遍历 LD_LIBRARY_PATH后面带上上文的路径即可
3. 动态库是如何被加载到内存?
3.1程序地址空间
在文件被编译编程成可执行程序时,会有一套自己的内部地址.与PCB私有的栈帧模型类似
将代码放在了代码区,只读变量放在了只读变量区等…
他私有的地址,当其放入内存时,就有物理地址自动与其一一对应
当cpu访问对应代码时,会从内存中将其放入页表上,此时已经有物理地址与虚拟地址的映射.直接放入即可.
动态库与普通文件无异,也会被加载到内存中。当文件需要调用时,将其映射到PCB的共享区.
多个PCB可以映射同一个动态库文件.errno用到的技术为写时拷贝
函数内部调用会跳转到函数的虚拟地址.
若该函数为动态库的函数,则该函数存储的是从共享区开头到目标动态库函数的偏移量.
这就是动态库编译选项中与位置无关码FPIC的含义
当cpu访问对应代码时,会从内存中将其放入页表上,此时已经有物理地址与虚拟地址的映射.直接放入即可.
动态库与普通文件无异,也会被加载到内存中。当文件需要调用时,将其映射到PCB的共享区.
多个PCB可以映射同一个动态库文件.errno用到的技术为写时拷贝
函数内部调用会跳转到函数的虚拟地址.
若该函数为动态库的函数,则该函数存储的是从共享区开头到目标动态库函数的偏移量.
这就是动态库编译选项中与位置无关码FPIC的含义
版权归原作者 ppeua 所有, 如有侵权,请联系我们删除。