动态库的制作和使用
1. 制作步骤
- 将所有要加入库的源文件编译为目标文件(与位置无关的目标文件(后面讲内存会提到))
- gcc -c -fPIC add.c mul.c
- 将第一步生成的目标文件打包到动态库文件中
- gcc -shared -o libp_math.so add.o mul.o
- 使用动态链接库生成可执行文件
- 在解决依赖(把动态库文件放到/usr/lib 或者 /lib下)的前提下直接 gcc main.o -lp_math
- 如果只是改变了 LD_LIBRARY_PATH, 链接目标文件时要加库路径 gcc main.o -L. -lp_math
- 不管那种方式解决依赖, a.out 都可以正常执行可执行文件, 只是以 第一种方式解决后, 链接起来不用再加-L库文件路径
2. 解决动态库依赖
- 查看可执行文件对动态库的依赖
- ldd 可执行文件
- 告知动态链接器如何找到动态库
- LD_LIBRARY_PATH
- /usr/lib 或者 /lib
3. 代码实例
p_math.h (定义各个模块间的接口)
// 如果没有定义了这个宏, 就定义这个宏,紧接着进行函数声明, 最后endif. 如果定义过, 就直接跳到endif
#ifndef P_MATH_H_ //为了避免头文件重复包含
#define P_MATH_H_
//函数声明
int t_add(int, int);
int t_sub(int, int);
int t_mul(int, int);
int t_div(int, int);
#endif
add.c (加法模块)
#include "p_math.h"
int t_add(int x, int y){
return x + y;
}
int t_sub(int x, int y){
return x - y;
}
mul.c (乘法模块)
#include "p_math.h"
int t_mul(int x, int y) {
return x * y;
}
int t_div(int x, int y) {
return x / y;
}
main.c
#include "p_math.h"
#include <stdio.h>
int main(void){
int val_x = 6, val_y = 2;
printf("%d + %d = %d\n", val_x, val_y, t_add(val_x, val_y));
printf("%d * %d = %d\n", val_x, val_y, t_mul(val_x, val_y));
}
- 制作步骤
# 1. 将所有要加入库的源文件编译为目标文件(与位置无关的目标文件)
$ gcc -c -fPIC add.c mul.c
# 2. 将第一步生成的目标文件打包到动态库文件中
$ gcc -shared -o libp_math.so add.o mul.o
# 3. 使用动态链接库生成可执行文件
$ gcc main.o -L. -lpmath
# a.out 和动态库是两个模块, 要执行a.out, 必须把动态库加载到内存当中
# 否则 a.out 找不到依赖的动态库
$ a.out
a.out: error while loading shared libraries: libp_math.so: cannot open shared object file: No such file or directory
# ldd可以查看可执行程序依赖的动态库, libp_math.so 找不到
$ ldd a.out
linux-vdso.so.1 => (0x00007fff7b390000)
libp_math.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbb49bd0000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbb49f9a000)
# 两种常用解决动态库依赖的方法
#1. 通过 LD_LIBRARY_PATH 环境变量告诉连接器去哪里找依赖的动态库
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
$ ldd a.out linux-vdso.so.1 => (0x00007fffd30a3000)
libp_math.so (0x00007fed51b12000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fed51748000)
/lib64/ld-linux-x86-64.so.2 (0x00007fed51d14000)
$ a.out
6 + 2 = 8
# 2. 把动态库文件放到链接器默认查找路径下
# 链接器(动态, 静态)默认路径是 /lib|/usr/lib, 把动态库移动到这下面
$ sudo mv libp_math.so /lib/.
$ ldd a.out
linux-vdso.so.1 => (0x00007ffc3e36d000)
libp_math.so => /lib/libp_math.so (0x00007f674cd0f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f674c945000)
/lib64/ld-linux-x86-64.so.2 (0x00007f674cf11000)
$ a.out
6 + 2 = 8
# 所以如果在链接main.o以前就把 libp_math.so 放到/lib/. 下,
# 链接的时候直接gcc main.o 就可以, 不用在写-L 直接写 -l 就可
$ rm a.out
$ gcc main.o -lp_math
$ a.out
6 + 2 = 8
4. 动态库优点
动态库和静态库区别:
- 可执行程序链接好以后不再依赖静态库
- 可执行程序和动态库是两个独立模块, 前者依赖后者.
所以可以直接更新动态库,不更新可执行程序
把 add.c d 代码改为
#include "p_math.h"
int t_add(int x, int y){
return x + y + 1;
}
int t_sub(int x, int y){
return x - y;
}
$ gcc -c -fPIC add.c
$ gcc -shared -o libp_math.so add.o mul.o
$ sudo mv libp_math.so /lib/.
$ gcc main.o -lp_math
$ a.out
6 + 2 = 9
$ nm a.out
# 静态库会把add模块的所以函数都考进来 T t_add T t_sub 地址确定
# 而动态库只会把使用到那个模块的函数标记过来 U t_add 地址不确定
....
00000000004006d6 T main
U printf@@GLIBC_2.2.5
0000000000400650 t register_tm_clones
00000000004005e0 T _start
U t_add
0000000000601040 D __TMC_END__
....
5. 动态链接器
- 自动加载: 一种是程序加载到内存执行的时候,将依赖的动态文件加载到内存, 再进行函数的链接, 也就是以上
- 动态加载: 另一种是程序运行的过程中, 需要使用到动态库中的函数时候(后面讲到动态加载时说)