动静态库
- 前言
- 引入
- 一、静态库
- 1. 创建静态库
- ①原理
- ②创建
- 2. 使用静态库
- ①借助编译选项
- ②只需要带库名
- 3. 小结
- 二、动态库
- 1. 创建动态库
- 2. 使用动态库
- 三、 动态库加载原理——进程地址空间
- 1. 地址
- ①程序没有被加载前的地址
- ②程序加载后的地址
- 2. 原理
- ①动态库的地址
- ②原理
前言
讲编译工具gcc时,讲到链接库,其中说静态链接和动态链接的相关知识,并且介绍了一些拓展命令,eg:od/file/ldd/readelf
接下来,目标:深入理解动静态库
引入
当我们想把我们写的代码给别人使用有两种方式:
- 直接把我们写的源文件和头文件给别人。
- 把源代码打包成库,加上头文件给别人。(库+.h)
优劣:
- 第一种:相当于把底层的东西直接暴露给使用者。
- 第二种:相当于只把使用说明(头文件)展示给使用者,使用者只知道如何调用方法,而不知道底层具体如何实现。
一、静态库
1. 创建静态库
①原理
- 静态库其实就是一堆.o文件打包形成
- 要先把源文件提前编译成目标文件
- 所以生成的库,人是读不出来的。
②创建
2. 使用静态库
开发者的库(xxx.a) + 使用者编写的代码(xxx.c) = 可执行程序(xxx.exe)
①借助编译选项
测试代码:
- 直接编译一下试试:
结果:运行失败
结论:我们这里测试的头文件,不在系统指定的目录下(usr/include),所以gcc找不到这个头文件.
- 告诉编译器,头文件的位置。使用
-I
选项,后面跟头文件所在路径即可
命令:gcc main.c -I ./lib/include
结果:运行失败
结论:这里不再是找不到头文件了,使用我们指定头文件路径这个是正确的,但是结果中的报错,是变量未定义报错,那很明显是链接报错,找不到静态库
- 告诉编译器,库的位置。使用
-L
选项,后面跟库文件所在路径即可
命令:gcc main.c -I ./lib/include -L ./lib/mymathlib
结果:运行失败
结论:指定库文件路径依旧不行,因为还要指定库文件名。为什么头文件就可以不指定文件名,那是因为在main.c中已经包含了头文件名
- 告诉编译器库的名称。使用
-l
选项,后紧跟库名。注:库名称是去掉lib和.a后缀。libmymath.a:mymath就是库名
命令:gcc main.c -I ./lib/include -L ./lib/mymathlib -lmymath
运行结果:
②只需要带库名
方法:
- 把头文件和库放到系统指定路径下,就可以只带
-l
编译选项。其实这个动作就是库的安装- 头文件和库在系统指定的目录下,建立软链接,就可以只带
-l
编译选项(这个下面演示一下)
演示:给头文件和库在系统指定目录下建立软链接
- 对头文件建立软链接
- 查看系统指定目录下的软链接:建立软链接成功
- 因为是在
/usr/include
目录下建立了一个路径的软链接,所以在测试使用的要包含头文件的方式要改变
- 运行程序
命令:gcc main.c -L ./lib/mymathlib -lmymath
注意:因为头文件的软链接被放到系统的指定目录下,所以就不需要使用-I
选项
运行结果:
- 对静态库建立软链接
- 运行程序
命令:gcc main.c -I ./lib/include -lmymath
注:因为库的软链接被放到系统的指定目录下,所以就不需要使用-L
选项
运行结果:
注:把头文件和库在系统指定的目录下,一起建立软链接,就可以只适用
-l
选项。上面的演示,我没有放在一起,所以要么带L
选项,要么就要带I
选项
3. 小结
使用总结:
- 大写i(
I
)指定头文件所在路径- 大写l(
L
)指定库所在路径- 小写l(
l
)指定库名称。注:库名称是去掉lib和.a后缀
静态库总结:
- 把头文件和库都移动到系统的指定目录下就可以不加选项
I和L
,但是只要使用第三方库必定要使用gcc -l[库名]
(第一二方,可以理解为系统和语言层次的库)- 在系统指定目录下建立软链接也可以不加选项
I和L
- 理解库中的全局变量
- 如果系统只提供静态库,则编译器就只能进行静态链接
- 可以链接多个库
二、动态库
1. 创建动态库
创建动态库的两个关键命令
- 生成目标文件:
gcc -fPIC -c $^
- 生成动态库:
gcc -shared -o $@ $^
生成动态库,并且分类:
注:发现生成的动态库具有可执行权限
虽然具有可执行权限,但是并不能执行
可执行权限:以可执行程序的方式加载到内存
2. 使用动态库
测试代码:
- 编译:根据对静态库使用的编译选项,指定路径测试动态库
命令:gcc test.c -I ./mylib/include -L ./mylib/lib -lmymethod
结果:编译成功,生成了可执行程序a.out- 运行:
结果:运行失败,没有发现共享库注:ldd命令:显示可执行程序链接的动态库
- 原因:
- 因为是动态库要加载到内存。编译器确实知道了动态库的位置,但是我系统不知道——加载器
- 解决:
- 拷贝到系统默认的库路径/lib64 or /usr/lib64(最常用,我们以后使用的库,都是别人成熟的库,所以可以直接安装都系统指定的目录下)
- 在系统默认的库路径(/lib64 or /usr/lib64)下建立软链接
- 将自己的库所在的路径,条件到系统的环境变量LD_LIBRARY_PATH中
- /etc/ld.so.conf.d建立自己的动态库路径的配置文件,然后加载(ldconfig)
这里测试一下后面两种解决办法,前面俩种拷贝和建立软链接就不测试了
- 环境变量:
- 注:重启Xshell之后,环境变量就恢复了,所以如果想一直可以,就要在配置文件中进行添加
- 建立自己的动态库路径的配置文件
- 注:
- 添加配置文件操作需要在root账号下
- 进入
/etc/ld.so.conf.d
目录下- 创建一个以
.conf
为后缀的文件,名字任意- 文件内填入需要使用的动态库路径
ldconfig
更新如果还要添加别的路径下的动态库,需要再创建文件然后写入路径
注:外部库很多,eg:ncurses库——基于终端的图形界面库(可以上网搜下载一下玩玩)
三、 动态库加载原理——进程地址空间
- 动态库在进程运行的时候,是要被加载的
- 常见的动态库被所有的可执行程序使用(eg:c标准库就被Linux很多指令共享使用)。动态库 —— 共享库
所以动态库在系统加载后,会被所以进程共享
1. 地址
①程序没有被加载前的地址
问题:程序在编译好之后,没有运行前,内部有地址吗?
通过命令,可以把编译好的程序反汇编出来
命令:objdump -S a.out
所以程序编译好之后,内部就有地址了,而且现在的编译器大多会采用平坦模式。这个地址就是虚拟地址,更准确的说是逻辑地址,对于目前来说,二者并没有什么区别,所以还没加载到内存,这个虚拟地址已经出现了
注:
平坦模式:内存管理模式,它将整个内存地址空间视为一个连续的线性地址空间,从0递增,程序可以直接使用线性地址进行访问
②程序加载后的地址
把可执行程序加载到内存,必然要先形成好自己的PCB
图解:
2. 原理
①动态库的地址
图解:
所以也得出为什么采用fPIC(产生位置无关码),因为直接用偏移量对库中的函数进行编址。而静态库就不需要这样做,因为静态库是直接拷贝到可执行程序中去的,直接编址就可以
②原理
图解:
- 结论:虚拟地址和物理地址建立映射,从此执行任何的代码,都是在我们的进程地址空间中执行的
- 事实:系统在运行的时候,一定存在很多个动态库。所以OS就得管理起来 —— 先描述再组织。所以系统中所有库的加载情况,OS都清楚