linux静态链接库
库
库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)
静态库
之所以称为“静态库”,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。
特点
- 可以实现共享代码
- 不能再包含其他静态链接库
- 本身包含了代码,地址符号表等,静态链接库的使用中,连接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中
- 代码装载速度快,执行速度略比动态链接库快
- 只需保证在开发者的计算机中有正确的 .a 文件,在以二进制形式发布程序时不需考虑在用户的计算机上 .a 文件是否存在及版本问题
- 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;
创建
- 编写源文件,通过 gcc -c xxx.c 生成目标文件
- 使用 ar 命令归档目标文件,生成静态库
- 配合静态库,写一个使用静态库中的函数的头文件
- 链接静态库,生成可执行文件
例子
源文件
add.c 文件:
int add(int a, int b) { return a + b; }
subtract.c 文件:
int subtract(int a, int b) { return a - b; }
编译两个文件,生成目标文件 add.o subtract.o
gcc -c add.c subtract.c
归档目标文件,生成静态链接库 libmath.a
使用 ar 命令将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索
ar rcv libmath.a add.o subtract.o
有关 ar 命令的使用,makepage 有详细的说明,常用的选项是:
c -- 如果库文件不存在,生成新的库文件,并不打印警告 r -- 代替库中已有的文件或者插入新的文件 v -- 输出详细信息 d -- 删除库中的某个目标文件 t -- 列出归档文件中包含的目标文件
我们生成的库文件名字必须形如 libxxx.a,这样我们在链接库的时候,就可以使用 -lxxx。
当我们链接时,指定 -lxxx,链接器就会在指定的目录查找 libxxx.a 或 libxxx.so 文件
编写头文件 mymath.h
#ifndef _MYMATH_H_ #define _MYMATH_H_ int add(int, int); int subtract(int, int); #endif
使用静态库
编写文件 main.c
#include <stdio.h> #include "mymath.h" int main() { printf("%d, %d\n", add(2, 3), subtract(9, 8)); return 0; }
编译链接
gcc main.c -L. -lmath
这里,-L 指定搜索链接库的包含当前目录
使用Makefile
.PHONY: all all: build target build: libmath.a libmath.a: add.o subtract.o ar rc $@ add.o subtract.o add.o: add.c gcc -c $< subtract.o: subtract.c gcc -c $< target: a.out a.out: main.c gcc main.c -L. -lmath .PHONY: clean clean: rm *.o libmath.a a.out
linux动态链接库
前言
静态链接库会编译进可执行文件,并被加载到内存,会造成空间浪费
静态链接库对程序的更新、部署、发布带来麻烦。如果静态库更新了,使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)
动态库在程序编译时并不会被链接到目标代码中,而是在执行文件中记录对动态库的引用,在程序运行时才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布带来的麻烦,用户只需要更新动态库即可,增量更新。
Linux下动态库文件的文件名形如 libxxx.so,其中so是 Shared Object 的缩写,即可以共享的目标文件。
特点
- 动态库把对一些库函数的链接载入推迟到程序运行的时期
- 可以实现进程之间的资源共享。(因此动态库也称为共享库)
- 将一些程序升级变得简单。
- 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)
生成动态链接库
- 编写源文件
- 将一个或几个源文件编译链接,生成共享库。
- 通过 -L<path> -lxxx 的gcc选项链接生成的libxxx.so。
- 把 libxxx.so 放入链接库的标准路径,或指定 LD_LIBRARY_PATH,才能运行链接了libxxx.so的程序。
例子
源文件
add.c
int add(int a, int b) { return a + b; }
subtract.c
int subtract(int a, int b) { return a - b; }
编译
gcc -fPIC -c add.c gcc -fPIC -c subtract.c
其中,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性
链接
gcc -shared -o libmymath.so subtract.o add.o
-shared是链接选项,告诉gcc生成动态库而不是可执行文件
这两步可以合并成一个命令:
gcc -o libmymath.so -fPIC -shared subtract.c add.c
编写头文件
#ifndef _MYMATH_H_
#define _MYMATH_H_
int add(int, int); int subtract(int, int);
#endif
使用链接库
main.c
#include <stdio.h> #include "mymath.h" int main() { printf("%d, %d\n", add(2, 3), subtract(9, 8)); return 0; }
链接
gcc main.c -L. -lmymath
如果同一目录下同时存在同名的动态库和静态库,比如 libmymath.so 和 libmymath.a 都在当前路径下,则gcc会优先链接动态库。
运行
当执行 ./a.out,发现程序报错
./a.out: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
这涉及到程序如何定位共享库文件
- 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)
- 对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段 -- 环境变量LD_LIBRARY_PATH -- /etc/ld.so.cache文件列表 -- /lib/,/usr/lib 目录找到库文件后将其载入内存。
/etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的
所以,我们想让程序找到自己写的动态链接库,有如下方法:
- 指定 LD_LIBRARY_PATH 环境变量为 libmymath.so 文件所在的目录
- 编辑/etc/ld.so.conf文件,加入库文件所在目录的路径; 同时,运行ldconfig ,该命令会重建/etc/ld.so.cache文件
- 把 libmymath.so 拷贝到/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作
我们可以执行:
LD_LIBRARY_PATH=. ./a.out
Makefile
.PHONY: all all: build target build: libmymath.so libmymath.so: add.o subtract.o gcc -o $@ -shared $^ add.o: add.c gcc -c -fPIC $< subtract.o: subtract.c gcc -c -fPIC $< target: a.out a.out: main.c gcc main.c -L. -lmymath .PHONY: clean clean: rm *.o a.out libmymath.so