目录
一.动静态库的概念
制作库的本质就是将程序生成出目标二进制文件, 将这些二进制文件打包, 并且将库中的函数的声明放到一个头文件中, 这个打包好的一堆目标文件也就被称为库, 当使用者想要使用这些库的时候, 只需要安装(本质是找到), 并且#include头文件, 就可以使用库中对应的函数了
静态库: .a结尾的文件
直接将库与文件和为一体, 所以一般采用静态链接生成的可执行文件很大, 因为采用静态地址编译链接, 直接将库中的内容编译链接到了可执行文件中, 每次执行可执行程序时都需要加载, 但好处是这个可执行文件可以不依赖库, 从而达到单独运行
动态库: .so结尾的文件
如果一个可执行文件使用/链接的动态库, 其文件大小一般比使用静态库要小很多, 但是可执行文件不能独立运行, 需要依赖于动态库, 动态库与可执行文件可以达到分批加载, 先将程序加载到内存, 当进程运行到需要使用动态库时, 这时将动态库加载到内存, 并且通过进程的共享区与加载到物理内存的动态库产生映射, 所以动态库只需要加载一次, 无论有几个进程需要用到这个动态库, 只需要将加载好的进程与动态库之间做映射关系就能够找到库的地址, 故在运行期间动态寻址
gcc对于头文件的默认搜索路径: /usr/include
gcc对于库文件的默认搜索路径: /lib64或/usr/lib64
对于gcc而言, 在编译时, 会自动去以上路径中搜索对应的标准库, 如果使用的是第三方库, 分为多种情况
1.如果使用的第三方库的头文件在/usr/include目录下, 库在/lib64或/usr/lib64下, gcc是可以默认搜索到的, 编译时只需要加-l库名称, 写上具体使用的库的名字即可
2.如果使用的第三方库不在gcc默认搜索路径中, 则gcc无法找到, 需要加-I与-L选项, 来手动添加搜索路径, 并且也需要-l来提供库名, gcc会去指定的目录下搜索
二.静态库的制作与使用
实验: 通过编写一个静态库, 来了解静态库的制作, 与静态库的使用
目标: 自己实现加法与减法, 然后包装成一个第三方库, 并用一个main.c程序调用这个库(静态库)
1.先实现出myadd.c mysub.c myadd.h mysub.h
2.生成并且发布静态库
使用命令: ar -rc lib静态库名称.a xx1.o xx2.o, 将xx1.o与xx2.o等目标文件包装成一个libxxx.a静态库
发布库: 形成好静态库之后, 创建一个目录output, 在output中创建两个目录, 分别是include存放头文件, lib存放刚刚包装好的库
3.使用(第三方)静态库
我们刚刚创建并且发布了一个静态库, 但我们并没有把它放到对应的gcc默认的系统搜索路径中, 也就是说它完全是一个第三方库, 我们在gcc编译时需要手动添加搜索路径
gcc编译命令: gcc main.c -o mybin -I(大写i) 头文件路径 -L 库路径 -l(小写L)库名
注意:
gcc编译指令如果不加-static, 默认是动态链接形成的可执行文件
此时链接的库分为三种情况:
1.没有静态库, 没有动态库: 直接报错
2.既有静态库, 也有动态库: 默认优先链接动态库
3.动静态库只有其中一种: 默认优先链接动态库, 如果此时没有动态库, 则只能静态链接所使用到的静态库
在第三种情况中, 当不存在动态库只有静态库时, 只好将被使用到的库的部分与静态库链接, 其余仍是动态链接的
如果想直接链接静态库, 并且整个可执行文件就是静态链接而成的, 就要加上-static选项, 静态链接
三.动态库的制作与使用
实验: 通过编写一个动态库, 来了解动态库的制作, 与动态库的使用
目标: 自己实现加法与减法, 然后包装成一个第三方库, 并用一个main.c程序调用这个库(动态库)
1.先实现出myadd.c mysub.c myadd.h mysub.h
2.动态生成目标文件
gcc -fPIC -c xxx.c -o xxx.o, 加上-fPIC选项, 生成动态目标文件(地址是相对的, 动态的)
3.生成并且发布动态库
使用命令gcc -shared xxx1.o xxx2.o -o lib动态库名称.so, 生成动态库
发布库: 形成好静态库之后, 创建一个目录output, 在output中创建两个目录, 分别是include存放头文件, lib存放刚刚包装好的库
4.使用(第三方)动态库
gcc编译命令: gcc main.c -o mybin -I(大写i) 头文件路径 -L 库路径 -l(小写L)库名
但是虽然编译成功了, 我们是无法正常运行mybin的, 使用ldd命令查看mybin提示, 动态库无法被找到
虽然我们在gcc编译时明确写出了头文件所在位置, 动态库所在位置, 动态库的名称, 虽然我们全部明确了, 但是这是对于gcc而言, 只是编译阶段时做的事, 现在确实成功编译链接, 成功的生成了可执行程序mybin, 但是在运行期间, 在/lib64或/usr/lib64默认查找路径查找, 但一般第三方库不会写到这个路径下, 故无法找到动态库的, 动态库与可执行文件分开加载, 在运行时调用动态库, 再将动态库加载到内存, 这时就也需要明确出动态库所在路径
如何解决: 三种方法
1). 改变LD_LIBRARY_PATH环境变量(系统默认库加载时的/运行时搜索路径), 将动态库所在路径添加到该环境变量中, 但这是一次性的, 下次登陆环境变量就会恢复
2). 更改配置文件, 目录:/etc/ld.so.conf.d/, 更改好配置文件之后使用命令ldconfig更新(刷新缓冲)即可永久化生效
改变配置文件, 本质: 在/etc/ld.so.conf.d 目录下创建一个新的.conf文件(sudo权限创建), 然后在新创建的文件中写入动态库路径即可
之后不要忘记ldconfig刷新一下(本质是在刷新缓冲区) , 即可
3). 简单做法, 简单粗暴, 直接将动态库添加到/lib64或/usr/lib64路径下, 或者可以创建一个该动态库的软连接放到/lib64或/usr/lib64路径下
注意: 如果添加软链接, 则软链接名称必须与动态库名称相同, lib开头, .so结尾
四.总结
库的制作:
制作静态库指令: ar -rc 静态库名(lib开头.a结尾) 目标文件
制作动态库指令: gcc -shared 目标文件(动态) 动态库名(lib开头.so结尾)
生成动态目标文件: gcc -fPIC -c xxx.c -o xxx.o
库的使用:
使用静态库: 在形成可执行文件时, 库就已经和程序融为一体了, 只需要在gcc编译时找到库与头文件即可
分为多种情况
1.(不推荐)可以直接将头文件放到/usr/include, 将库放到/lib64或/usr/lib64, 这两个路径为系统默认库搜索路径(gcc与加载器会使用)
指令: gcc main.c -o mybin -l(小写L)库名(此库名不需lib开头与.a结尾)
2.(推荐)为了不污染原生标准库搜索路径, 采用在编译时手动写入路径的方式
指令: gcc main.c -o mybin -I(大写i) 头文件路径 -L 库路径 -l(小写L)库名(此库名不需lib开头与.a结尾)
使用动态库: 形成可执行文件与库并无直接关系, 程序与库分批加载, 所以在运行时(加载器)需要能够找到库的所在位置
0.对于编译阶段, 采用上述方法解决gcc编译的问题
解决对于运行时查找库位置的问题
1.修改LD_LIBRARY_PATH环境变量, 添加动态库路径, 一次性的
2.修改配置文件, 在路径/etc/ld.so.conf.d下创建一个新.conf文件, 并且将动态库路径写入到新创建的文件之, 永久有效
3.直接将动态库添加到/lib64或/usr/lib64中, 或者添加该动态库的软链接, 但如果添加软链接, 一定注意名称问题, 一定要与动态库名称一致
lib开头(中间为自定义动态库名).so结尾
动静态链接的优缺点:
动态链接
优点:
1.更加节省内存并减少页面交换
2.库文件与程序文件独立, 只要输出接口不变, 更换库文件不会对程序文件造成任何影响, 因而极大地提高了可维护性和可扩展性
3.不同编程语言编写的程序只要按照函数调用约定就可以调用同一个库函数
4.适用于大规模的软件开发, 使开发过程独立, 耦合度低, 便于不同开发者和开发者组织之间进行开发和测试
缺点:
1.运行时依赖, 否则找不到库文件
2.运行时加载速度相对静态库较慢, 因为需要通过起始地址与偏移量来计算虚拟地址
3.需要对库版本之间的兼容性做出更多处理
静态链接的优缺点:
优点:
1.简单, 直接将库编译进可执行文件, 直接确定了库的地址
缺点:
1.直接将库文件编译进可执行文件中, 使可执行文件占用空间较多, 浪费内存和磁盘空间
2.模块更新困难, 因为库与程序之间耦合度较高
3.每次有新模块的更新都需要重新编译打包整个代码