- 作用
- 分离编译
- 代码重用
- 分类
分类 | 作用 | 后缀 |
---|---|---|
静态库 | 一个或多个.o 目标文件归档在一个文件中 |
.a |
共享库 | 没有main 函数的可执行文件 |
.so |
动态加载库 | 没有main 函数的可执行文件,接口复合API |
.so |
0.前提(文件内容)
test.cpp
#include <iostream>
#include "test.h"
using namespace std;
void Func(int i){
cout << __func__ << "(" << i << ")" << endl;
}
- test.h
#ifndef __TEST_H_
#define __TEST_H_
void Func(int i);
#endif
- main.cpp
#include "test.h"
int main(){
Func();
return 0;
}
1. 静态库的制作与使用
1.1 创建
- 编译源文件
g++ -c -o test.o test.cpp
- 生成静态库
ar -rcs libtest.a test.o
-
ar
选项
命令选项 | 作用 |
---|---|
r |
替换模块(replace) |
c |
创建库(create) |
s |
建立索引 |
tar
和ar
都是归档工具
tar
用于创建.tar
归档文件。ar
用于创建归档文件,并且为归档的目标文件中的符号建立索引。
- 查看目标文件的符号(symbol)信息
nm 目标文件
目标文件可以是.o
、.a
,也可以是可执行文件。
1.2 使用
- 链接静态库
g++ -o main main.cpp -L. -ltest
或者
g++ -o main main.cpp ./libtest.a
注意:库一定要放在命令行的末尾
- 测试
./main
- 结果
Func(100)
2. 共享库的制作
2.1 创建
- 编译目标文件
g++ -c -fPIC test.cpp -o test.o
- 生成动态库
g++ -shared test.o -o libtest.so
以上两步可以合并为
g++ -shared -fPIC -o libtest.so test.cpp
- 选项说明
命令选项 | 作用 |
---|---|
shared |
创建动态库 |
fPIC |
代码都是与位置无关的 |
每个共享函数库都有个特殊的名字,称作
soname
。soname
名字命名必须以lib
作为前缀,然后是函数库的名字,然后是.so
,最后是版本号信息。不过有个特例,就是非常底层的C库函数都不是以lib
开头这样命名的。
2.2 使用
- 生成可执行文件
g++ -o main main.cpp -L. -ltest
或者
g++ -o main main.cpp ./libtest.so
注意:库一定要放在命令行的末尾
- 测试
指定动态链接库位置
export LD_LIBRARY_PATH=动态链接库位置
执行
./main
- 结果
Func(100)
关于动态链接库的安装路径
- 如果安装在
/lib
或者/usr/lib
下,那么ld
默认能够找到,无需其他操作。- 如果安装在其他目录,需要将其添加到
/etc/ld.so.cache
文件中,步骤如下:
编辑/etc/ld.so.conf
文件,加入库文件所在目录的路径
运行ldconfig
,该命令会重建/etc/ld.so.cache
文件
当静态库和动态库同名时, gcc命令将优先使用动态库。
- 查看执行文件链接的动态链接库
ldd 可执行文件
也可以查看动态链接库所链接的其它动态库。
3. 动态加载库
3.1 创建
- 修改
test.h
#ifndef __TEST_H_
#define __TEST_H_
#ifdef __cpluscplus
extern "C" //C++
{
#endif
void Func(int i);
#ifdef __cpluscplus
}
#endif
#endif // __TEST_H_
extern
关键字:在一个项目中必须保证函数、变量、枚举等在所有的源文件中保持一致,除非你指定定义为局部的。
extern "C"
指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言
extern "C"
的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern "C"
,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。__cplusplus
的值是为了表示C++的版本,目前不应该依赖该宏的值。
- 修改
main.cpp
#include <iostream>
#include <cstdlib>
#include <dlfcn.h>
using namespace std;
int main(){
void *so_handle = dlopen("./libtest.so", RTLD_LAZY); // 加载.so文件
if (!so_handle) {
cerr << "Error: load so failed" << endl;
exit(-1);
}
typedef void func_t(int);
func_t *pFunc = (func_t *)dlsym(so_handle,"Func");
char *err = dlerror();
if (NULL != err) {
cerr << "load Func err:" << err << endl;
exit(-1);
}
pFunc(100);
dlclose(so_handle); // 关闭so句柄
return 0;
}
- 重新编译
.so
g++ -shared -fPIC test.cpp -o libtest.so
3.2 使用
- 重新编译
main.cpp
g++ -o main main.cpp -ldl
- 执行
.\main
- 结果
Func(100)
3.3 动态库(共享库、动态加载库)与静态库的区别
- 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
- 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。
3.4 动态加载与静态加载的区别
- 动态加载
- 灵活,可以在需要的时候进行加载,在不需要的时候进行卸载,这样可以不必占用内存。
- 可以在没有动态库时候发现,而不致程序报错。
- 加载程序中有条件才运行的库。
- 热更新,在不停止程序的前提下进行更新。
- 复杂一些,需要显示获得函数地址。
- 静态加载
- 简单方便
- 没有找到动态库时,系统报错
- 加载运行很久的库
4. 总结
5. 补充
使用动态库的方法还有如下几种
-
方法一:连接前,添加动态库目录到环境变量
LD_RUN_PATH
。export LD_RUN_PATH=动态库目录
方法二:编译链接时,添加链接选项
-Wl,-rpath -Wl,动态库目录
-
方法三:执行前,添加动态库目录到环境变量
LD_LIBRARY_PATH
。export LD_LIBRARY_PATH=动态库目录
-
方法四:添加共享库目录
/usr/local/lib
到共享库配置文件echo 动态库目录 >> /etc/ld.so.conf ldconfig