• 作用
  1. 分离编译(防止拷贝源码,用二进制文件,直接调用)
  2. 代码重用
  • 分类
分类作用后缀
静态库一个或多个.o目标文件归档在一个文件中+头文件.a
共享库没有main函数的可执行文件.so
动态加载库没有main函数的可执行文件,接口复合API.so

0.前提(文件内容)

1.准备:将原来的array.h,array.cpp拆分成两个文件;
array.h, alg.h,array.cpp, alg.cpp

  • array.h
 #ifndef __ARRAY_H
#define __ARRAY_H

#ifdef __cplusplus
    extern "C"{
#endif // __cplusplus

void* seq_create();
void seq_destroy(void* seq);
void seq_append(void* seq,int val);
void seq_prepend(void* seq,int val);
int seq_size(void* seq);
int seq_get(void* seq,int index);
#ifdef __cplusplus
}
#endif // __cplusplus


#endif // __ARRAY_H
  • alg.h
#ifndef __ALG_H
#define __ALG_H

#ifdef __cplusplus
    extern "C"{
#endif // __cplusplus

// C语言不认识extern "C"
int sum(int* arr,int n);
void reverse(int* arr,int n);
int max_element(int* arr,int n);

#ifdef __cplusplus
}
#endif // __cplusplus


#endif // __ALG_H
  • array.cpp
//#include "vector.h"
#include <vector>
#include "array.h"
using namespace std;
//using namespace miniSTL;

void* seq_create(){
    return new vector<int>();
}
void seq_destroy(void* seq){
    delete reinterpret_cast<vector<int>*>(seq);
}
void seq_append(void* seq,int val){
    reinterpret_cast<vector<int>*>(seq)->push_back(val);
}
void seq_prepend(void* seq,int val){
    vector<int>* p = reinterpret_cast<vector<int>*>(seq);
    p->insert(p->begin(),val);
}
int seq_size(void* seq){
    return reinterpret_cast<vector<int>*>(seq)->size();
}
int seq_get(void* seq,int index){
    return reinterpret_cast<vector<int>*>(seq)->at(index);
}
  • alg.cpp
#include "alg.h"
#include <algorithm>
#include <numeric>
using namespace std;

int sum(int* arr,int n){
    return accumulate(arr,arr+n,0);
}

void reverse(int* arr,int n){
    reverse(arr,arr+n);
}

int max_element(int* arr,int n){
    return max_element(arr,arr+n) - arr;
}
  • array_test.cpp
#include "array.h"
#include "alg.h"
#include <iostream> // 在CPP不使用io
#include <algorithm>
using namespace std;

int main(){
    int arr[] = {1,3,6,2,7};
    cout << sum(arr,5) << endl;
    for_each(arr,arr+5,[](int n){cout << n << " ";});
    cout << endl;
    reverse(arr,5);
    for_each(arr,arr+5,[](int n){cout << n << " ";});
    cout << endl;
    cout << max_element(arr,5) << endl;
}
  • makefile
  1. 源变量的定义;结果变量的定义
OBJS = array.o alg.o array_test.o
DEST = array_test
  1. 假象目标
    .PHONY: all clean

  2. 结果的编译的过程

all:$(DEST)

3.1 结果的依赖性

$(DEST):$(OBJS)  

3.2 gcc 运算符,结果的生成

@echo 创建测试程序$(DEST)
$(CXX) $(CXXFLAGS) $^ -o $@  ##2.2gcc 运算符,结果的生成
  1. 依赖源的编译的过程
    ##3.1源的依赖性
    ##3.2gcc运算符,源的生成;
$(OBJS):%.o:%.cpp   
	@echo 编译$(OBJS)  ##3.1源的依赖性
	$(CXX) $(CXXFLAGS) $< -c -o $@  ##3.2gcc运算符,源的生成;
  1. 清空
clean:
@echo 清空
rm -f $(DEST) $(OBJS)
  • 完整案例
#1. 源变量的定义;结果变量的定义
OBJS = array.o alg.o array_test.o
DEST = array_test

.PHONY: all clean

all:$(DEST)
#2 . 结果的编译的过程
$(DEST):$(OBJS)  ##2.1结果的依赖性
	@echo 创建测试程序$(DEST)
	$(CXX) $(CXXFLAGS) $^ -o $@ -std=c++11 ##2.2gcc 运算符,结果的生成

#3 .依赖源的编译的过程
$(OBJS):%.o:%.cpp   
	@echo 编译$(OBJS)  ##3.1源的依赖性
	$(CXX) $(CXXFLAGS) $< -c -o $@  -std=c++11 ##3.2gcc运算符,源的生成;

clean:
	@echo 清空
	rm -f $(DEST)  $(OBJS)   

1. 静态库的制作与使用

1.1 创建

  1. 编译源文件,生成可执行文件
注意:-c(排除main函数的影响,最好用),
-o的用法,前面是结果(.o),后面是源文件(.cpp)
 g++ -c -o array.o array.cpp
 g++ -c -o alg.o alg.cpp
  1. 生成静态库,并打包
注意:关于main,测试函数的内容不打包;
-参数含义
1ar打包
2r替换模块(replace)
-c创建库(create)
-s建立索引
3libarrayalg.a库名:前面必须有lib,后面必须有.a
4alg.o array.o打包的可执行程序,不包括main
ar -rcs libarrayalg.a alg.o array.o
tar和ar都是归档工具
tar用于创建.tar归档文件。
ar用于创建归档文件,并且为归档的目标文件中的符号建立索引。
  1. 查看目标文件的符号(symbol)信息
nm 目标文件
nm libarrayalg.a
nm array.o
nm alg.o

目标文件可以是.o、.a,也可以是可执行文件。

  1. 链接静态库
    方案1:带编译的结果的名字;注意:(1)不带前后缀(2)-L -l写在后面; (3)-L当前路径的.不能丢
g++ -o  array_test array_test.o -L. -liarrayalg #方案1:带编译的结果的名字;注意:1)不带前后缀(2-L -l写在后面; (3-L当前路径的.不能丢

方案2:带编译的结果的名字;

 g++  array_test.o -L. -liarrayalg #方案2:带编译的结果的名字;

方案3:直接写路径

g++  array_test.o ./libarrayalg.a   #方案3:直接写路径

注意:库一定要放在命令行的末尾

  • 测试
  ./a.out
  ./array_test

2. 共享库的制作

2.1 创建

  1. 编译目标文件
g++ -c -fPIC test.cpp -o test.o
  1. 生成动态库
   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 使用

  1. 生成可执行文件
 g++ -o main main.cpp -L. -ltest

或者

  g++ -o main main.cpp ./libtest.so

注意:库一定要放在命令行的末尾

  1. 测试
    指定动态链接库位置
 export LD_LIBRARY_PATH=动态链接库位置
  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. 动态加载库

topic:前面两种都属于静态链接库,需要重启更新;第三种属于动态连接,需要手动处理,需要的时候加载,不需要的时候,可以不用

3.1 初级版本

3.1.1 创建

  • 完整代码
  1. 修改array.h
#ifndef __ARRAY_H
#define __ARRAY_H

#ifdef __cplusplus
    extern "C"{
#endif // __cplusplus

void* seq_create();
void seq_destroy(void* seq);
void seq_append(void* seq,int val);
void seq_prepend(void* seq,int val);
int seq_size(void* seq);
int seq_get(void* seq,int index);
#ifdef __cplusplus
}
#endif // __cplusplus


#endif // __ARRAY_H
  • extern关键字:在一个项目中必须保证函数、变量、枚举等在所有的源文件中保持一致,除非你指定定义为局部的。
    extern "C"指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言
    extern "C"的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern “C”,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。
  • __cplusplus的值是为了表示C++的版本,目前不应该依赖该宏的值。
  1. 修改array_test.cpp
    2.0. 头文件
#include <dlfcn.h>  //0.头文件

2.1 采用句柄的手段(智能指针,函数指针的用法)

 void* so_handle = dlopen("./libarralg.so",RTLD_LAZY);
    if(NULL == so_handle){
        cerr << "open dll error" << endl;
        return 1;
    }
dlclose(so_handle);
so_handle = NULL;

2.2 采用句柄的手段(智能指针,函数指针的用法)+强制类型转化

 int (*sum)(int*, int) = (int(*)(int*,int))dlsym(so_handle,"sum");
    if(NULL == so_handle){
        cerr << "sum load error" << endl;
        return 1;}
  • 完整代码
#include "array.h"
//#include "alg.h"
#include <iostream> // 在CPP不使用io
#include <algorithm>
#include <dlfcn.h>  //0.头文件
using namespace std;


int main(){
    // 1.手动加载动态库采用句柄的手段(智能指针,函数指针的用法)
    void* so_handle = dlopen("./libarralg.so",RTLD_LAZY);
    if(NULL == so_handle){
        cerr << "open dll error" << endl;
        return 1;
    }
    
 // 2.手动加载动态库采用的函数;
    int (*sum)(int*, int) = (int(*)(int*,int))dlsym(so_handle,"_Z3sumPii");
    if(NULL == so_handle){
        cerr << "sum load error" << endl;
        return 1;}
    void (*reverse)(int*, int) = (void(*)(int*,int))dlsym(so_handle,"_Z7reversePii");
    if(NULL == so_handle){
        cerr << "reverse  load error" << endl;
        return 1;}
    int (*max_element)(int*, int) = (int(*)(int*,int))dlsym(so_handle,"_Z11max_elementPii");
    if(NULL == so_handle){
        cerr << "max_element load error" << endl;
        return 1;}

auto sum = LoadFunc<ifunc_t>(so_handle,"_Z3sumPii");
    auto reverse = LoadFunc<vfunc_t>(so_handle,"_Z7reversePii");
    auto max_element = LoadFunc<ifunc_t>(so_handle,"_Z11max_elementPii");






    int arr[] = {1,3,6,2,7};
    cout << sum(arr,5) << endl;
    for_each(arr,arr+5,[](int n){cout << n << " ";});
    cout << endl;
    reverse(arr,5);
    for_each(arr,arr+5,[](int n){cout << n << " ";});
    cout << endl;
    cout << max_element(arr,5) << endl;


    dlclose(so_handle);
    so_handle = NULL;

}

3.1.2 两种情况

3.1.2.1 (命令行)

g++ -fPIC array.cpp -c
g++ -shared array.o -o libarrayalg.so
g++ array_test.cpp -ldl
./a.out

3.1.2.2 (makefile)

  1. 重新编译dynamic_makefile

3.1 创建动态库的时候加参数

$(CXX) -shared -fPIC -o $(LIB) $^ 

3.2 编译的时候程序进行修改(-ldl)

$(CXX) $(CXXFLAGS) $< -o $@ -ldl
  • 完整代码 dynamic_makefile
OBJS = array.o alg.o
LIB = libarralg.so
DEST = array_test
CXXFLAGS = -g
#CXXFLAGS = -g

.PHONY: all clean

all:$(DEST)

$(DEST):array_test.cpp $(LIB)
	@echo 创建测试程序$(DEST)
	$(CXX) $(CXXFLAGS) $< -o $@ -ldl

$(LIB):$(OBJS)
	@echo 创建动态库$(LIB)
	$(CXX) -shared -fPIC -o $(LIB) $^ 

$(OBJS):%.o:%.cpp
	@echo 编译$(OBJS)
	$(CXX) $(CXXFLAGS) $< -fPIC -c -o $@

clean:
	@echo 清空
	rm -f $(DEST) array_test.o $(OBJS) $(LIB)
然后编译 make all
make clean

3.1.3 使用

  1. 执行
.\array_test

3.2 进阶版本

对array_test.cpp进行修改

  1. 模板的使用
template<typename F>
F LoadFunc(void* handle,const char* name){
    F func = reinterpret_cast<F>(dlsym(handle,name));
    if(NULL == func){
        cerr << name << " is not find" << endl;
    }
    return func;
} 

合并的写法

typedef int(* ifunc_t)(int*,int);
    typedef void(* vfunc_t)(int*,int);

    auto sum = LoadFunc<ifunc_t>(so_handle,"_Z3sumPii");
    auto reverse = LoadFunc<vfunc_t>(so_handle,"_Z7reversePii");
    auto max_element = LoadFunc<ifunc_t>(so_handle,"_Z11max_elementPii");
  • 完整代码
#include "array.h"
//#include "alg.h"
#include <iostream> // 在CPP不使用io
#include <algorithm>
#include <dlfcn.h>
using namespace std;

template<typename F>
F LoadFunc(void* handle,const char* name){
    F func = reinterpret_cast<F>(dlsym(handle,name));
    if(NULL == func){
        cerr << name << " is not find" << endl;
    }
    return func;
} 

int main(){
    // 手动加载动态库
    void* so_handle = dlopen("./libarralg.so",RTLD_LAZY);
    if(NULL == so_handle){
        cerr << "open dll error" << endl;
        return 1;
    }
    
    typedef int(* ifunc_t)(int*,int);
    typedef void(* vfunc_t)(int*,int);

    auto sum = LoadFunc<ifunc_t>(so_handle,"_Z3sumPii");
    auto reverse = LoadFunc<vfunc_t>(so_handle,"_Z7reversePii");
    auto max_element = LoadFunc<ifunc_t>(so_handle,"_Z11max_elementPii");

    
    /*
    ifunc_t sum = (ifunc_t)dlsym(so_handle,"sum");
    if(NULL == sum){
        cerr << "sum load error" << endl;
        return 1;
    }
    vfunc_t reverse = (vfunc_t)dlsym(so_handle,"reverse");
    if(NULL == reverse){
        cerr << "reverse load error" << endl;
        return 1;
    }
    ifunc_t max_element = (ifunc_t)dlsym(so_handle,"max_element");
    if(NULL == max_element){
        cerr << "reverse load error" << endl;
        return 1;
    }
*/

    int arr[] = {1,3,6,2,7};
    cout << sum(arr,5) << endl;
    for_each(arr,arr+5,[](int n){cout << n << " ";});
    cout << endl;
    reverse(arr,5);
    for_each(arr,arr+5,[](int n){cout << n << " ";});
    cout << endl;
    cout << max_element(arr,5) << endl;

 
    dlclose(so_handle);
    so_handle = NULL;

}

3.3 完整版本

  • array:array_test.cpp
#include "array.h"
//#include "alg.h"
#include <iostream> // 在CPP不使用io
#include <algorithm>
#include <dlfcn.h>
using namespace std;

template<typename F>
F LoadFunc(void* handle,const char* name){
    F func = reinterpret_cast<F>(dlsym(handle,name));
    if(NULL == func){
        cerr << name << " is not find" << endl;
    }
    return func;
} 

int main(){
    // 手动加载动态库
    void* so_handle = dlopen("./libarralg.so",RTLD_LAZY);
    if(NULL == so_handle){
        cerr << "open dll error" << endl;
        return 1;
    }
    
    typedef int(* ifunc_t)(int*,int);
    typedef void(* vfunc_t)(int*,int);

    auto sum = LoadFunc<ifunc_t>(so_handle,"_Z3sumPii");
    auto reverse = LoadFunc<vfunc_t>(so_handle,"_Z7reversePii");
    auto max_element = LoadFunc<ifunc_t>(so_handle,"_Z11max_elementPii");

    
    /*
    ifunc_t sum = (ifunc_t)dlsym(so_handle,"sum");
    if(NULL == sum){
        cerr << "sum load error" << endl;
        return 1;
    }
    vfunc_t reverse = (vfunc_t)dlsym(so_handle,"reverse");
    if(NULL == reverse){
        cerr << "reverse load error" << endl;
        return 1;
    }
    ifunc_t max_element = (ifunc_t)dlsym(so_handle,"max_element");
    if(NULL == max_element){
        cerr << "reverse load error" << endl;
        return 1;
    }
*/

    int arr[] = {1,3,6,2,7};
    cout << sum(arr,5) << endl;
    for_each(arr,arr+5,[](int n){cout << n << " ";});
    cout << endl;
    reverse(arr,5);
    for_each(arr,arr+5,[](int n){cout << n << " ";});
    cout << endl;
    cout << max_element(arr,5) << endl;

    typedef void*(*seq_create_t)();
    typedef void (*seq_destroy_t)(void*);
    typedef void (*seq_append_t)(void*,int);
    typedef int (*seq_size_t)(void*);
    typedef int (*seq_get_t)(void*,int);

    auto seq_create = LoadFunc<seq_create_t>(so_handle,"seq_create");
    auto seq_destroy = LoadFunc<seq_destroy_t>(so_handle,"seq_destroy");
    auto seq_append = LoadFunc<seq_append_t>(so_handle,"seq_append");
    auto seq_size = LoadFunc<seq_size_t>(so_handle,"seq_size");
    auto seq_get = LoadFunc<seq_get_t>(so_handle,"seq_get");

    void* seq = seq_create();
    for(int i=0;i<10;++i){
        seq_append(seq,i);
    }
    for(int i=0;i<seq_size(seq);++i){
        cout << seq_get(seq,i) << " ";
    }
    cout << endl;
    seq_destroy(seq);


    dlclose(so_handle);
    so_handle = NULL;

}

3.4动态加载库的问题

出错翻译改正
redefine xxx重复定义不同的.cpp文件可能定义了相同的函数,需要手动删除,保留其中的一个;
dump吐核(1)编译的时候: -g (2)函数名字写错,导致在动态库没有生成对应的库函数;可以用压淬后的文件名
cannot find shared路径的问题export LD_LIBRARY_PATH=.

3.5 动态库(共享库、动态加载库)与静态库的区别

  • 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
  • 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。在这里插入图片描述
    在这里插入图片描述

3.6 动态加载与静态加载的区别

  • 动态加载
  1. 灵活,可以在需要的时候进行加载,在不需要的时候进行卸载,这样可以不必占用内存。
  2. 可以在没有动态库时候发现,而不致程序报错。
  3. 加载程序中有条件才运行的库。
  4. 热更新,在不停止程序的前提下进行更新。
  5. 复杂一些,需要显示获得函数地址。
  • 静态加载
  1. 简单方便
  2. 没有找到动态库时,系统报错
  3. 加载运行很久的库

4. 总结

在这里插入图片描述
静态库、共享库与动态库编译链接使用比较
在这里插入图片描述
静态库与动态库Make file比较

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
  1. 参考
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐