阅读量:0
文章目录
1. 认识静态库与动态库
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
- 一个动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制,允许物理内存中的一份动态库被 需要用到该库的所有进程 共用,节省了内存和磁盘空间。
本文测试准备代码:
// ---------- add.h ---------- #ifndef __ADD_H__ #define __ADD_H__ int add(int a, int b); #endif // ---------- add.c ---------- #include "add.h" int add(int a, int b) { return a + b; } // ---------- sub.h ---------- #ifndef __SUB_H__ #define __SUB_H__ int sub(int a, int b); #endif // ---------- sub.c ---------- #include "sub.h" int sub(int a, int b) { return a - b; } // ---------- main.c ---------- #include <stdio.h> #include "add.h" #include "sub.h" int main() { int a = 10; int b = 20; printf("add(%d, %d) = %d\n", a, b, add(a, b)); a = 100; b = 20; printf("sub(%d, %d) = %d\n", a, b, sub(a, b)); return 0; }
2. 手动创建并测试静态库
2.1 生成静态库
我们先使用命令生成一个静态库,并测试是否可以使用:
# 把上面的代码拿过来 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l total 20 -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:13 add.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:13 add.h -rw-rw-r-- 1 ubuntu ubuntu 243 Apr 20 20:33 main.c -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:15 sub.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:14 sub.h # 生成 .o 文件,准备打包为静态库 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc -c add.c -o add.o ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc -c sub.c -o sub.o # 生成静态库 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ar -rc libmymath.a add.o sub.o # ar 是 gnu 归档工具,常用于将目标文件打包为静态库 # rc 表示(replace and create) # 查看静态库中的目录列表 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ar -tv libmymath.a rw-r--r-- 0/0 1376 Jan 1 08:00 1970 add.o rw-r--r-- 0/0 1376 Jan 1 08:00 1970 sub.o # t:列出静态库中的文件 # v:verbose 详细信息 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc main.c -L. -lmymath # -L:指定库路径 # -l:指定库名 # 测试静态库 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls add.c add.h add.o a.out libmymath.a main.c sub.c sub.h sub.o ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ./a.out add(10, 20) = 30 sub(100, 20) = 80 # 测试目标文件生成后,静态库删掉,程序照样可以运行 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ rm libmymath.a ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ./a.out add(10, 20) = 30 sub(100, 20) = 80
2.2 打包静态库
如果想把这个静态库给别人使用,我们需要将 库(.a)和 头文件(.h)一起打包;这样别人在看到头文件的时候,就知道我们的库中封装了哪些方法。
使用 Makefile 进行自动化编译:
# ---------- Makefile ---------- libmymath.a:sub.o add.o ar -rc $@ $^ %.o:%.c gcc -c $< .PHONY:clean clean: rm -rf *.o output libmymath.a # 将相关文件打包到 output 文件夹 .PHONY:output output: mkdir output cp -rf *.h output cp libmymath.a output
# ubuntu 20.04 实机演示 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l total 24 -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:13 add.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:13 add.h -rw-rw-r-- 1 ubuntu ubuntu 243 Apr 20 20:33 main.c -rw-rw-r-- 1 ubuntu ubuntu 188 Apr 21 13:38 Makefile -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:15 sub.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:14 sub.h # 构建静态库 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ make gcc -c sub.c gcc -c add.c ar -rc libmymath.a sub.o add.o ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l total 36 -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:13 add.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:13 add.h -rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 add.o -rw-rw-r-- 1 ubuntu ubuntu 2960 Apr 21 13:43 libmymath.a -rw-rw-r-- 1 ubuntu ubuntu 243 Apr 20 20:33 main.c -rw-rw-r-- 1 ubuntu ubuntu 188 Apr 21 13:38 Makefile -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:15 sub.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:14 sub.h -rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 sub.o # 打包 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ make output mkdir output cp -rf *.h output cp libmymath.a output ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l total 40 -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:13 add.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:13 add.h -rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 add.o -rw-rw-r-- 1 ubuntu ubuntu 2960 Apr 21 13:43 libmymath.a -rw-rw-r-- 1 ubuntu ubuntu 243 Apr 20 20:33 main.c -rw-rw-r-- 1 ubuntu ubuntu 188 Apr 21 13:38 Makefile drwxrwxr-x 2 ubuntu ubuntu 4096 Apr 21 13:43 output -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:15 sub.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:14 sub.h -rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 sub.o # 查看 output ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ tree output/ output/ ├── add.h ├── libmymath.a └── sub.h 0 directories, 3 files
2.3 使用静态库
我们已经将静态库以及相关头文件打包到了 output
文件夹,现在我们新建一个 TestStaticLib
文件夹进行测试:
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ mkdir TestStaticLib ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls -l total 44 -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:13 add.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:13 add.h -rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 add.o -rw-rw-r-- 1 ubuntu ubuntu 2960 Apr 21 13:43 libmymath.a -rw-rw-r-- 1 ubuntu ubuntu 243 Apr 20 20:33 main.c -rw-rw-r-- 1 ubuntu ubuntu 188 Apr 21 13:38 Makefile drwxrwxr-x 2 ubuntu ubuntu 4096 Apr 21 13:43 output -rw-rw-r-- 1 ubuntu ubuntu 61 Apr 20 20:15 sub.c -rw-rw-r-- 1 ubuntu ubuntu 65 Apr 20 20:14 sub.h -rw-rw-r-- 1 ubuntu ubuntu 1376 Apr 21 13:43 sub.o drwxrwxr-x 2 ubuntu ubuntu 4096 Apr 21 13:56 TestStaticLib # 将 output 移动到 TestStaticLib 文件夹,并改名为 StaticLib ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ mv output TestStaticLib/ ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ cd TestStaticLib/ ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ mv output StaticLib ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ ls StaticLib
在 TestStaticLib
目录中新建测试文件:
// ---------- test.c ---------- #include <stdio.h> #include "add.h" #include "sub.h" int main() { int a = 10; int b = 20; printf("add(%d, %d) = %d\n", a, b, add(a, b)); a = 100; b = 20; printf("sub(%d, %d) = %d\n", a, b, sub(a, b)); return 0; }
使用 Makefile 进行自动化编译:
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ ls StaticLib test.c ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ touch Makefile # 编写 Makefile ubuntu:~/Linux/test_4_20/TestStaticLib$ vim Makefile ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ cat Makefile test:test.c gcc -o $@ $^ -I ./StaticLib -L ./StaticLib -l mymath # -I:指定头文件所在目录 # -L:指定静态库所在目录 # -l:指定库名 .PHONY:clean clean: rm -f test
测试:
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ make gcc -o test test.c -I ./StaticLib -L ./StaticLib -l mymath ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ ls -l total 32 -rw-rw-r-- 1 ubuntu ubuntu 99 Apr 21 14:21 Makefile drwxrwxr-x 2 ubuntu ubuntu 4096 Apr 21 13:43 StaticLib -rwxrwxr-x 1 ubuntu ubuntu 16816 Apr 21 14:21 test -rw-rw-r-- 1 ubuntu ubuntu 243 Apr 21 14:13 test.c ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestStaticLib$ ./test add(10, 20) = 30 sub(100, 20) = 80
3. 库搜索路径
- 从左到右搜索 -L 指定的目录
- 由环境变量指定的目录(LIBRARY_PATH)
- 由系统指定的目录
- / user / lib
- / user / local / lib
下面测试使用动态库时,需要我们在库搜索路径中手动添加动态库!
4. 手动创建并测试动态库
4.1 生成动态库
使用命令手动生成静态库:
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls add.c add.h main.c sub.c sub.h ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc -fPIC -c sub.c add.c # fPIC:产生位置无关码(position independent code) # 编译产生的代码没有绝对位置,只有相对位置,从而可以在任意地方调用生成的动态库 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls add.c add.h add.o main.c sub.c sub.h sub.o ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ gcc -shared -o libmymath.so *.o # shared:表示生成共享库格式 # 库名规则:lib*.so ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls add.c add.h add.o libmymath.so main.c sub.c sub.h sub.o
4.2 打包动态库
将 动态库(.so)和 头文件(.h)打包到一个文件夹:
# ---------- Makefile ---------- libmymath.so:add.o sub.o gcc -shared -o $@ $^ %.o:%.c gcc -fPIC -c $< .PHONY:clean clean: rm -rf libmymath.so *.o DynamicLib .PHONY:DynamicLib DynamicLib: mkdir DynamicLib cp *.h DynamicLib cp libmymath.so DynamicLib
# ubuntu 20.04 实机演示 # 构建动态库 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls add.c add.h main.c Makefile sub.c sub.h TestStaticLib ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ make gcc -fPIC -c add.c gcc -fPIC -c sub.c gcc -shared -o libmymath.so add.o sub.o ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls add.c add.o main.c sub.c sub.o add.h libmymath.so Makefile sub.h TestStaticLib # 打包 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ make DynamicLib mkdir DynamicLib cp *.h DynamicLib cp libmymath.so DynamicLib ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ ls add.c add.o libmymath.so Makefile sub.h TestStaticLib add.h DynamicLib main.c sub.c sub.o # 查看打包文件 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ tree DynamicLib/ DynamicLib/ ├── add.h ├── libmymath.so └── sub.h 0 directories, 3 files
4.3 使用动态库
故技重施,创建 TestDynamicLib
文件夹进行测试:
# 把上面的动态库拿过来,并创建 Makefile 和 test.c ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ tree TestDynamicLib/ TestDynamicLib/ ├── DynamicLib │ ├── add.h │ ├── libmymath.so │ └── sub.h ├── Makefile └── test.c # test.c 与静态库保持一致 # Makefile ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ cat TestDynamicLib/Makefile test:test.c gcc -o $@ $^ -I ./DynamicLib -L ./DynamicLib -l mymath .PHONY:clean clean: rm -f test
直接编译试试?
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20$ cd TestDynamicLib/ # 编译 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ make gcc -o test test.c -I ./DynamicLib -L ./DynamicLib -l mymath ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ ls DynamicLib Makefile test test.c # 运行 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ ./test ./test: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
发现报错了,系统找不到我们的动态库,为什么?
上面我们提到过库搜索路径,我们只是在编译时指定了路径,只有编译器知道动态库的位置,编译完成后就没有人知道去哪里找动态库了!所以可执行程序是找不到动态库的!
那为什么静态库没事呢?因为静态库是直接把库链接到可执行文件中,编译完成后把静态库删掉都不会影响可执行文件。
但动态库不一样,程序每次执行都要去磁盘上搜索动态库的位置,然后链接,由于我们并没有指定动态库的位置,所以找不到!
如何解决?
一个比较简单且没有副作用的方法是修改 LD_LIBRARY_PATH
环境变量,因为我们只是想测试,并不想真的把这个动态库添加到系统中,而环境变量在我们关掉 xshell 后就会重置,所以我们选择这种方法进行测试:
ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ pwd /home/ubuntu/Linux/test_4_20/TestDynamicLib # 修改环境变量 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ export LD_LIBRARY_PATH=/home/ubuntu/Linux/test_4_20/TestDynamicLib/DynamicLib # 查看环境变量 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ echo $LD_LIBRARY_PATH /home/ubuntu/Linux/test_4_20/TestDynamicLib/DynamicLib ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ ls DynamicLib Makefile test test.c # 执行测试程序 ubuntu@VM-20-5-ubuntu:~/Linux/test_4_20/TestDynamicLib$ ./test add(10, 20) = 30 sub(100, 20) = 80 # 测试成功
5. 动静态库优先级
- 如果我们同时提供动态库和静态库,gcc 默认使用的是动态库;
- 如果我们非要静态链接,我们必须使用 static 选项;
- 如果我们只提供静态库,那我们的可执行程序也没办法,只能对该库进行静态链接,但是程序不一定整体是静态链接的。
- 如果我们只提供动态库,默认只能动态链接,如果非要静态链接,会发生链接报错!