前言
库文件(专门存储可重复使用的代码块,包含大量函数、类和方法的文件)是一种最常用的共享代码的方式,根据使用方法不同,分为 静态链接库 和 动态链接库
库文件分类
项目的运行会通关过 编译 和 链接 两个过程
- 编译:编译器将项目中的源文件进行词法、语法分析等操作,最终生成多个目标文件。每个目标文件都是二进制文件,由于它们会相互调用对方的函数或变量或调用某些链接库文件中的函数或变量,编译器无法跨文件找到它们确切的存储地址,所以这些目标文件无法单独执行。
- 链接:链接器负责修复各个目标文件中缺失的函数和变量的存储地址,最终将所有的目标文件和链接库组织成一个可执行文件。
静态链接方式: 无论缺失的地址位于其它目标文件还是链接库,链接库都会逐个找到各目标文件中缺失的地址。采用此链接方式生成的可执行文件,可以独立载入内存运行。
动态链接方式: 链接器(此部分为静态链接器作用)先从所有目标文件中找到 部分 缺失的地址,然后将所有目标文件组织成一个可执行文件。待文件执行时,需连同所有的链接库(动态链接库)文件一起载入内存,再由链接器(此部分为动态链接器作用)完成剩余的地址修复工作,才能正常执行。
通常将第一种链接方式称为静态链接,用到的链接库称为 静态链接库 ;第二种链接方式中,链接所有目标文件的方法仍属静态链接,而载入内存后进行的链接操作称为动态链接,用到的链接库称为 动态链接库 。
静态库对比动态库
区别 | 静态库 | 动态库 |
---|---|---|
链接时机 | 生成可执行文件之前完成所有链接操作 | 将部分链接操作推迟到程序执行时才进行 |
内存空间占用 | 1、可执行文件内部拷贝了所有目标文件和静态链接库的指令和数据,文件本身的体积会很大 2、当系统中存在多个链接同一个静态库的可执行文件时,每个可执行文件中都存有一份静态库的指令和数据,就会造成内存空间的极大浪费 | 1、动态链接库和可执行文件是分别载入内存的,因此动态链接库的体积通常会小一些 2、多个程序使用同一个动态链接库时,所有程序可以共享一份动态链接库的指令和数据,避免了空间的浪费 |
维护成本 | 一旦程序中有模块更新,整个程序就必须重新链接后才能运行 | 只需要将旧的模块替换掉,程序运行时会自动将所有模板载入内存并动态地链接在一起 |
加工成链接库的条件
源文件中只提供可以重复使用的代码,例如函数、设计好的类等,不能包含 main 主函数;
源文件在实现具备模块功能的同时,还要提供访问它的接口,也就是包含各个功能模块声明部分的头文件。
静态库的创建与使用
1、将所有指定的源文件,都编译成相应的目标文件
gcc -c ./*.c
2、然后使用 ar 压缩指令,将生成的目标文件打包成静态链接库
// ar rcs 静态链接库名称 目标文件1 目标文件2
// Windows 下名称为 xxx.lib,Linux 下为 xxx.a
ar rcs libmymath.lib ./*.o
3、首先我们将 main.c 文件编译为目标文件
gcc -c main.c
4、完成链接操作,-static 选项强制 GCC 编译器使用静态链接库
gcc -static main.o libmymath.a
//如果 GCC 编译器提示无法找到 libmymath.a,还可以使用如下方式完成链接操作
//-L 向编译器指明静态链接库的存储位置, -l指明所需静态链接库的名称
gcc main.o -static -L /libpath -lmymath
动态库的创建与使用
动态链接库的创建方式有 2 种
方法一:
/// -shared 选项用于生成动态链接库
/// -fpic 令编译器生成动态链接库时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用。
/// gcc -fpic -shared 源文件名... -o 动态链接库名
/// Linux 其后缀名用 .so 表示;Windows 后缀名为 .dll
1、gcc -fpic -shared ./*.c -o libmymath.so
方法二:
1、先将指定源文件编译为目标文件,为了后续生成动态链接库并能正常使用,将源文件编译为目标文件时,也需要使用 -fpic 选项。
gcc -c -fpic ./*.c
2、利用上一步生成的目标文件,生成动态链接库
gcc -shared ./*.o -o libmymath.so
3、使用
gcc main.c libmymath.so -o main.exe
在动态库的使用过程中,通过执行 ldd xx.exe
指令,可以查看当前可执行文件在执行时需要用到的所有动态链接库,以及各个库文件的存储位置
运行由动态链接库生成的可执行文件时,必须确保程序在运行时可以找到这个动态链接库。常用的解决方案有如下几种:
- 将链接库文件移动到标准库目录下(例如 /usr/lib、/usr/lib64、/lib、/lib64)
- 在终端输入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
,其中xxx
为动态链接库文件的绝对存储路径(此方式仅在当前终端有效,关闭终端后无效) - 修改
~/.bashrc
或~/.bash_profile
文件,即在文件最后一行添加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
(xxx 为动态库文件的绝对存储路径)。保存之后,执行source .bashrc
指令(此方式仅对当前登陆用户有效)
以上方式作为动态链接库的隐式调用,即将动态链接库与其他源程序文件一起参与链接
动态链接库的显式调用
C/C++ 程序中显示调用动态链接库,无需引入和动态链接库相关的头文件。手动调用动态链接库中包含的资源,同时用完后要手动将资源释放。
/// 显式调用动态链接库,需要使用该头文件提供的一些函数
#include <dlfcn.h>
/**
打开库文件,其本质就是将库文件装载到内存中,为后续使用做准备。
filename:表明目标库文件的存储位置和库名,
以 / 开头绝对路径表示的文件名,则函数会前往该路径下查找库文件;
如果用户仅提供文件名,则该函数会依次前往 LD_LIBRARY_PATH 环境变量指定的目录、
/etc/ld.so.cache 文件中指定的目录、/usr/lib、/usr/lib64、/lib、/lib64 等默认搜索路径中查找
flag:
RTLD_NOW:将库文件中所有的资源都载入内存;
RTLD_LAZY:暂时不降库文件中的资源载入内存,使用时才载入。
*/
void *dlopen (const char *filename, int flag);
/**
获得指定函数在内存中的位置
hanle 参数表示指向已打开库文件的指针;symbol 参数用于指定目标函数的函数名。
成功找到指定函数,会返回一个指向该函数的指针;反之如果查找失败,函数会返回 NULL
*/
void *dlsym(void *handle, char *symbol);
/**
关闭已打开的动态链接库
返回 0 时,表示函数操作成功;反之,函数执行失败。
*/
int dlclose (void *handle);
/**
最近一次 dlopen()、dlsym() 或者 dlclose() 函数操作失败的错误信息
函数返回 NULL,则表明最近一次操作执行成功
*/
const char *dlerror(void);