创建calc.c
文件
#include "calc.h"
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
创建calc.h
文件
// 计算功能的头文件
#ifndef __CALC_H
#define __CALC_H
int add(int, int);
int sub(int, int);
#endif
创建show.c
文件
#include
#include "show.h"
void show(int l, char op, int r, int res)
{
printf("%d %c %d = %d\n", l, op, r, res);
}
创建show.h
文件
// 输出函数的头文件
#ifndef __SHOW_H
#define __SHOW_H
void show(int, char, int, int);
#endif
对代码只进行编译而不进行链接:gcc -c calc.c
、gcc -c show
编写接口文件math.h
// 接口文件
#ifndef __MATH_H
#define __MATH_H
#include "calc.h"
#include "show.c"
#endif
ar
命令
ar [选项] <静态库文件> <目标文件列表>
-r
将目标插入到静态库中,已存在则更新-q
将目标文件追加到静态库尾-d
从静态库中删除目标文件-t
列表显示静态库中的目标文件-x
将静态库展开为目标文件使用这个命令制作静态库:ar -r libmath.a calc.o show.o
编写主函数main.c
#include "math.h"
int main(void)
{
int a = 100, b = 200;
show(a, '+', b, add(a, b)); // 使用add和show
show(a, '-', b, sub(a, b)); // 使用sub和show
return 0;
}
编译的时候一共有三个方式(linux中指定库名的时候默认将)
直接链接静态库:gcc main.c libmath.a -o test
用l指定库名,用-L指定库路径(因为我这个是在当前路径.):gcc main.c -lmath -L. -o test
将库路径添加到LIBRARY_PATH
中,然后使用-l
指定库名
export LIBRARY_PATH=$LIBRARY_PATH:/home/bhlu/study/static
gcc main.c -lmath -o test
直接执行:./test
calc.h
、calc.c
show.h
、show.h
math.h
gcc -c calc.c
gcc -c show.c
ar -r libmath.a calc.o show.o
gcc main.c -lmath -L. -o test
./test
动态库和静态库不同,链接动态库不需要将被调用的函数代码复制包含调用代码的可执行文件中,相反链接器会在调用语句处嵌入一段指令,在该程序执行时,会加载该动态库并寻找被调用函数的入口地址并执行;动态库的扩展名是.so
动态库的函数创建和静态库一致
创建calc.c
文件
#include "calc.h"
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
创建calc.h
文件
// 计算功能的头文件
#ifndef __CALC_H
#define __CALC_H
int add(int, int);
int sub(int, int);
#endif
创建show.c
文件
#include
#include "show.h"
void show(int l, char op, int r, int res)
{
printf("%d %c %d = %d\n", l, op, r, res);
}
创建show.h
文件
// 输出函数的头文件
#ifndef __SHOW_H
#define __SHOW_H
void show(int, char, int, int);
#endif
对代码只进行编译而不进行链接:gcc -c calc.c
、gcc -c show
编写接口文件math.h
// 接口文件
#ifndef __MATH_H
#define __MATH_H
#include "calc.h"
#include "show.c"
#endif
gcc -shared -fpic cala.o show.o -o libmath.so
gcc -shared -fpic cala.c show.c -o libmath.so
-fPIC
:大模式,生成代码比较大,运行速度比较慢,所有平台都支持-fpic
:小模式,生成代码比较小,运行速度比较快,仅部分平台支持编写库的使用代码main.c
#include "math.h"
int main(void)
{
int a = 100, b = 200;
show(a, '+', b, add(a, b)); // 使用add和show
show(a, '-', b, sub(a, b)); // 使用sub和show
return 0;
}
编译并链接动态库(这里编译也是有三种方式,与上面静态库一致)
直接链接动态库:gcc main.c libmath.so -o test
用l指定库名,用-L指定库路径(因为我这个是在当前路径.):gcc main.c -lmath -L. -o test
将库路径添加到LIBRARY_PATH
中,然后使用-l
指定库名
export LIBRARY_PATH=$LIBRARY_PATH:/home/bhlu/study/shared
gcc main.c -lmath -o test
编译成功后,在执行程序之前,这里必须配置链接器的环境变量,不然会报以下错误
# error while loading shared libraries: libmath.so: cannot open shared object file: No such file or directory
# 需要配置一下链接器到的环境变量
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/bhlu/study/shared
执行命令./test
calc.h
、calc.c
show.h
、show.h
math.h
gcc -c calc.c
gcc -c show.c
gcc -shared -fpic calc.o show.o -o libmath.so
gcc main.c -lmath -L. -o test
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/bhlu/study/shared
./test
在使用动态库的动态加载时,为了避免后面这种情况:假设一个程序执行了10分钟,而它用到动态库的地方就只需1分钟,为了实现动态库使用完就立即回收,从而减少内存的占用,我们采用了下面的这个操作方法。
load.c
(里面用的到的函数在代码后面会进行解释)
#include
#include
int main(void)
{
// 1. 将动态库载入内存-dlopen函数
void *handle = dlopen("/home/bhlu/study/shared/libmath.so", RTLD_NOW);
if(handle == NULL)
{
// 当载入不成功的时候
fprintf(stderr, "dlopen: %s\n", dlerror());
return -1;
}
// 2. 获取库中函数的地址-dlsym函数
int (*add)(int, int) = dlsym(handle, "add");
if(add == NULL)
{
fprintf(stderr, "dlsym: %s\n", dlerror());
return -1;
}
int (*sub)(int, int) = dlsym(handle, "sub");
if(sub == NULL)
{
fprintf(stderr, "dlsym: %s\n", dlerror());
return -1;
}
int (*show)(int, char, int, int) = dlsym(handle, "show");
if(show == NULL)
{
fprintf(stderr, "dlsym: %s\n", dlerror());
return -1;
}
// 3. 使用
int a = 100, b = 200;
show(a, '+', b, add(a, b));
show(a, '-', b, sub(a, b));
// 4. 卸载动态库
// dlclose(handle)
if(dlclose(handle))
{
// 判断是否卸载成功,其实当有其他程序再调用这个动态库的时候,是卸载不了的
fprintf(stderr, "dlclose: %s\n", dlerror());
return -1;
}
return 0;
}
使用相关命令编译load.c
:gcc load.c -ldl -o load
,因为这里用到了libld这个库,所以编译的时候需要指定一下,不过我在我的ubuntu环境下不加这个也可以,等之后有时间了解一下原因。
相关函数的介绍
dlfcn.h
这个头文件:#include
dlopen
函数
void *dlopen(char const *filename, int flag);
filename
:动态库路径,如果只给文件名,会根据LD_LIBRARY_PATH
环境变量去搜索,上面示例给的是绝对路径flag
:加载方式
RTLD_LAZY
:延时加载,使用动态库中的符号时才会真的加载进内存RTLD_NOW
:立即加载dlsym
函数
void *dlsym(void *handle, char const *symbol);
handle
:动态库访问句柄,即dlopen
函数的返回值symbol
:符号名,即函数名dlclose
函数
int dlclose(void *handle);
handle
动态库访问句柄dlerror
函数
char *dlerror(void);
上面使用到的fprintf
函数,因为这里想使用的是错误输出,所以使用的标准错误流stderr
,与标准输出流 stdout
不同,标准错误流用于输出错误和警告信息。
动态加载的辅助工具
查看符号表:nm
列出目标文件、可执行程序、静态库、动态库中的函数
nm libmath.a
# 输出
calc.o:
0000000000000000 T add
0000000000000018 T sub
show.o:
U printf
0000000000000000 T show
查看依赖:ldd
查看可执行文件或者共享库所依赖的共享库
ldd test
# 输出
linux-vdso.so.1 (0x00007fff3b0f5000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5d1da00000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5d1dd50000)