静态库
I)静态库相关
1)对静态库的使用发生在编译过程中,当编译完成,生成可执行文件后,即便删除静态库,也不影响文件执行
2)相比动态库在可执行文件执行时才加载库,由静态库编译生成的可执行文件执行效率可能会稍高一些,但是作为代价,对内存资源的消耗就相应较高
3)Linux下的静态库命名规则:lib + “库名” .a(例如:lib giraffe.a)
II) 静态库制作过程、使用
1)将所有需要添加到静态库的源文件(.c)编译为目标文件(.o):
gcc -c *.c
2)创建静态库并将目标文件添加进来:
ar -r lib+“库名”.a *.o
3)使用静态库文件链接目标文件生成可执行文件:
gcc -l"库名" -L“库文件路径” “目标文件名”.o
以便理解上述过程,下面用一个简单的计算函数举例说明:
III)静态库制作、使用举例
MyMath Linraffe$ ls
MyMath.h add.c div.c mul.c sub.c
~/Desktop/MyMath目录下存放着头文件和需要添加到静态库中的源文件,代码如下:
1 # ifndef __MYMATH_H__
2 # define __MYMATH_H__
3
4 int add(int ,int);
5 int sub(int ,int);
6 int mul(int ,int);
7 int div(int ,int);
8
9 # endif
以上是MyMath.h的代码
1 # include <stdio.h>
2 # include "MyMath.h"
3
4 int add(int i ,int j){
5 return i + j;
6 }
以上是add.c的代码,sub.c、mul.c、div.c形式与add.c完全相同
```bash
Desktop Linraffe$ ls -l MyTest.c
-rw-r--r-- 1 Linraffe staff 708 3 4 02:34 MyTest.c
1 # include <stdio.h>
2 # include "./MyMath/MyMath.h"
3 typedef int (*func_t)(int ,int);
4
5 int func_cb(func_t func_p,int a ,int b){
6 return func_p(a,b);
7 }
8
9 int main(void){
10 func_t func_arr[4] = {add,sub,mul,div};
11 int i,a,b;
12 a = 12,b = 3;
13 for(i = 0;i < 4;i++){
14 if(func_arr[i] == add){
15 printf("a + b = %d\n",func_cb(func_arr[i],a,b));
16 }
17 if(func_arr[i] == sub){
18 printf("a - b = %d\n",func_cb(func_arr[i],a,b));
19 }
20 if(func_arr[i] == mul){
21 printf("a * b = %d\n",func_cb(func_arr[i],a,b));
22 }
23 if(func_arr[i] == div){
24 printf("a / b = %d\n",func_cb(func_arr[i],a,b));
25 }
26 }
27 return 0;
28 }
以上是测试代码MyTest.c,存放在~/Desktop目录下
下面是静态库的制作和使用流程:
1)执行gcc -c *.c,将所有需要添加到静态库的源文件(.c)编译为目标文件(.o)
MyMath Linraffe$ gcc -c *.c
MyMath Linraffe$ ls
add.o div.o mul.o sub.o
2)创建静态库libmath.a并将所有目标文件(.o)添加到libmath.a:
ar -r lib+“库名”.a *.o //ar -r libmath.a *.o
MyMath Linraffe$ ar -r libmath.a *.o
ar: creating archive libmath.a
MyMath Linraffe$ ls
add.o div.o libmath.a mul.o sub.c sub.o
查看创建的动态库文件中的内容:
ar -t lib +“库文件名”.a //ar -t libmath.a
MyMath Linraffe$ ar -t libmath.a
__.SYMDEF SORTED
add.o
div.o
mul.o
sub.o
3)使用静态库文件(libmath.a)链接目标文件(MyTest.o)生成可执行文件(MyTest):
gcc -l"库名" -L“库文件路径” “目标文件名”.o
//gcc -lmath -L ~/Desktop/MyMath MyTest.o -o MyTest_s
执行MyTest,显示结果正确。
Desktop Linraffe$ gcc -lmath -L ~/Desktop/MyMath MyTest.o -o MyTest_s
Desktop Linraffe$ MyTest_s
a + b = 15
a - b = 9
a * b = 36
a / b = 4
4)补充:
- ar 命令的作用时将多个文件打包到一个文件中去,ar命令的基本操作是从打包文件中添加和删除文件
添加文件:ar -r “打包文件名” “要添加到打包文件中的文件名”
删除文件:ar -d“打包文件名” “要添加到打包文件中的文件名” - 步骤3)中gcc -l选项指定了编译器链接过程要链接的静态库文件名;
gcc -L选项指定了编译器链接过程中搜索要链接的静态库文件的路径
以上,虽然完成了静态库的制作,并可以顺利编译运行程序,但是代码的编写存在缺陷:
一、静态库文件libmath.a中存储的四个计算功能实现代码和测试程序包含的头文件的形式是“# include “MyMath.h””和“# include “./MyMath/MyMath.h””,这意味着头文件MyMath.h的路径一旦有所改动,静态库文件libmath.a的计算功能就会失效,测试程序也不能通过编译,因为找不到头文件。
解决方法:
将头文件放到编译器自动搜索头文件的路径下
执行gcc -v -c MyTest.c,gcc -v选项会把编译的详细过程输出在屏幕上,其中包含系统自动搜索头文件的路径,找到自动搜索路径,并将MyMath.h放在路径下便可解决这个问题
Desktop Linraffe$ gcc -v MyTest.o -o MyTest_s
输出结果:
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/include
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory)
End of search list.
Desktop Linraffe$
将MyMath.h放到/usr/local/include目录下,并将各源文件中的“#include"MyMath.h"”和“#include “./MyMath.h””全部替换为“#include <MyMath.h>”,再重执行1)到3)步就可以解决这个问题
二、静态库文件制作完成后,随意放在MyMath目录下容易造成丢失,给日后维护代码造成困难,最好是有专门存放静态库文件的目录,以便于维护。
解决方法:
将静态库文件libmath.a放到系统自动搜索静态库文件的路径下
执行gcc -lmath MyTest.c -o MyTest_s,编译报错,因为我们没有指定静态库文件的搜索路径(-L选项),但是编译信息反馈了编译器自动搜索静态库文件的搜索路径,我们只要找到该路径,并把libmath.h放到该路径下即可
Desktop Linraffe$ gcc -lmath MyTest.c -o MyTest_s
输出结果:
-L/usr/local/lib //这里只保留有用的信息,不是全部
将libmath.a放到/usr/local/目录下,并重新执行gcc -lmath MyTest.c -o MyTest_s即可。
注意:
- 这里执行gcc -lmath MyTest.c -o MyTest_s编译命令已经不需要-L选项了,因为系统会自动搜索我们刚添加的那个静态库文件路径,所以不需要在手动指定静态库路径了。
- 这个系统自动搜索的静态库文件路径可能因系统而异,本人是mac os系统,可能使用ubuntu或者fedora会有不同的路径结果。
以上是静态库相关笔记
动态库
I)动态库相关
1)对动态库的使用发生在可执行程序在内存上运行时,这意味着可执行程序的执行依赖动态库。这是动态库与静态库最大的区别
2)Linux下的静态库命名规则:lib + “库名” .so(例如:lib giraffe.so)
II)动态库制作过程、使用
1)将所有需要添加到动态库的源文件(.c)编译为与路径无关的目标文件(.o):
gcc -c -fPIC *.c
(这里对于路径无关的目标文件的含义会在下文中解释)
2)创建动态库并将与路径无关目标文件(.o)添加进来:
gcc -shared -o lib+“库名”.so *.o
3)使用动态库文件链接目标文件生成可执行文件:
gcc -l“库名” “目标文件名”.o
说明:这里要可执行程序运行之后,加载动态库文件的路径,方法有两种
- 将动态库文件放到系统自动搜索动态库文件的路径下(这个路径在静态库制作时已经提到过)
- 将动态库文件路径添加到环境变量LD_LIBRARY_PATH中去,例如:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库文件路径
以便理解上述过程,依然使用静态库制作时的计算程序,对动态库的制作使用举例:
III)静态库制作、使用举例
1)将所有需要添加到动态库的源文件(.c)编译为与路径无关的目标文件(.o):
gcc -c -fPIC *.c
MyMath Linraffe$ gcc -c -fPIC *.c
MyMath Linraffe$ ls
add.o div.o mul.o sub.o
2)创建动态库并将与路径无关目标文件(.o)添加进来:
gcc -shared -o lib+“库名”.so *.o
MyMath Linraffe$ gcc -shared -o libmath.so *.o
MyMath Linraffe$ ls
add.o div.o libmath.so mul.o sub.o
3)首先将动态库文件libmath.so添加到系统自动搜索动态库文件的搜索路径下或者将libmath.so的路径添加到环境变量LD_LIBRARY_PATH中,使得程序运行时系统明确去哪里加载指定的动态库文件。这里我使用的是第一种方法。
MyMath Linraffe$ sudo mv libmath.so /usr/local/lib
使用动态库文件链接目标文件生成可执行文件:
gcc -l“库名” “目标文件名”.o
并运行链接动态库的可执行文件(MyTest_d),发现执行结果与链接静态库编译生成的可执行文件(MyTest_s)运行结果一致。
Desktop Linraffe$ gcc -lmath MyTest.c -o MyTest_d
Desktop Linraffe$ MyTest_d
a + b = 15
a - b = 9
a * b = 36
a / b = 4
IV)动态库的应用场景
动态库的特点是主程序与功能的相对独立,当我们要对程序的功能进行升级时,我们不需要将整个程序重新编译,只需将动态库重新编译,再次运行程序时,就可以动态加载到升级后的功能函数,很方便。打网络游戏时通常有个客户端,就像一个主程序,当我们升级客户端时,其实就是在对现有的动态库进行替换升级。类似的软件在线升级都是对动态库的应用场景。
还是以上面这个计算程序为例,我们现在想升级(修改)sub函数的功能,只需修改mul函数的源文件,再重新编译动态库文件,并替换原来的动态库文件即可:
1)修改sub.c文件
1 # include <stdio.h>
2 # include <MyMath.h>
3 int sub(int i ,int j){
4 return 1024;
5 }
2)重新编译生成动态库文件
MyMath Linraffe$ gcc -c -fPIC *.c
MyMath Linraffe$ gcc -shared -o libmath.so *.o
3)替换动态库
MyMath Linraffe$ sudo mv libmath.so /usr/local/lib
4)再次执行MyTest_d
MyMath Linraffe$ MyTest_d
a + b = 15
a - b = 1024
a * b = 36
a / b = 4
可以看到,我们没有再次编译MyTest_d,再次运行该文件时,sub函数的执行结果已经不同了。
V)链接静态库、动态库编译生成的可执行文件比较
- 现在得到了两个可执行文件MyTest_s、MyTest_d分别是链接静态库libmath.so、动态库libmath.so生成的。
使用nm命令查看两个可执行文件:
Desktop Linraffe$ nm MyTest_s
U ___stack_chk_fail
U ___stack_chk_guard
0000000100000000 T __mh_execute_header
0000000100000d10 T _add
0000000100000d30 T _div
0000000100000d90 T _func_cb
0000000100000dc0 T _main
0000000100000d50 T _mul
U _printf
0000000100000d70 T _sub
U dyld_stub_binder
Desktop Linraffe$ nm MyTest_d
U ___stack_chk_fail
U ___stack_chk_guard
0000000100000000 T __mh_execute_header
U _add
U _div
0000000100000d90 T _func_cb
0000000100000dc0 T _main
U _mul
U _printf
U _sub
U dyld_stub_binder
对比可知,静态版本的可执行文件MyTest_s在加载到内存时,功能函数add、sub、mul、div都已经在内存上有了明确的地址。而动态版本的可执行文件MyTest_d在加载到内存时,功能函数add、sub、mul、div在内存中的位置是未知的,只有等到加载libmath.so才会分配具体地址。上文遗留的与位置无关的目标文件的定义,可以对比两个版本中四个功能函数加载到内存中的时机去理解。
- 当动态库文件与静态库文件同名(libmath.a、libmath.so),且两个文件放在统一目录下(/usr/local/lib)。当我们编译可执行文件时,会链接动态库还是静态库呢?我们看一下实验:
/usr/local/lib下存放了上文中我们制作的静态库文件和动态库文件
~ Linraffe$ cd /usr/local/lib
lib Linraffe$ ls libmath.
libmath.a libmath.so
编译测试文件,只指定库名(动态库、静态库文件同名,都是math),生成可执行文件MyTest_x
Desktop Linraffe$ gcc -lmath MyTest.c -o MyTest_x
查看MyTest_x所依赖的库:
对于ubuntu,使用ldd + 可执行文件名
对于mac os ,使用otool + L + 可执行文件名
Desktop Linraffe$ otool -L MyTest_x
MyTest_x:
libmath.so (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libSystem.B.dylib
可见,对于同名动静态库,在编译链接时,会默认选择动态库。
若是想要链接静态库,编译时需要加上选项“-static”
动态库函数动态加载
动态库函数动态加载与动态库区别
动态库在可执行程序运行后会将指定的动态库文件整体(比如,libmath.so中包含的所有目标文件)加载到内存中。而动态库函数动态加载,则是按需加载,只加载指定动态库文件中需要用到的部分函数。从而进一步节省了程序运行过程中的资源消耗。
动态库函数动态加载相关库函数
使用动态库函数动态加载需要包含<dlfcn.h>头文件
相关函数:
void* dlopen(const char* path, int mode)
dlopen函数打开path指定的动态库文件,mode指定立即加载(RTLD_NOW)还是延迟(RTLD_LAZY)加载动态库文件,打开失败返回函数返回NULL
int dlclose(void* handle)
dlclose函数关闭ldopen函数打开的动态库文件,
handle是dlopen函数的返回值,dlclose函数关闭文件成功返回0,失败返回非零值
void* dlsym(void* handle, const char* symbol)
dlsym函数在dlopen函数打开的动态库文件中寻找需要加载到内存的函数名symbol,如果找到则返回该函数应该加载到内存中的首地址,如果找不到返回NULL
const char* dlerror(void);
如果动态库函数动态加载操作有错误,dlerror函数会返回最近的一个错误的描述
动态库函数动态加载举例
修改测试函数MyTest.c代码为MyDlTest.c:
1 # include <stdio.h>
2 # include <MyMath.h>
3 # include <dlfcn.h>
4 typedef int (*func_t)(int ,int);
5
6 int main(void){
7 void *handle = dlopen("libmath.so",RTLD_LAZY);
8 if(!handle){
9 printf("%s\n",dlerror());
10 return -1;
11 }
12
13 int a,b;
14 a = 12,b = 3;
15
16 func_t fp_add = (func_t)dlsym(handle,"add");
17 if(!fp_add){
18 printf("%s\n",dlerror());
19 }
20 func_t fp_sub = (func_t)dlsym(handle,"sub");
21 if(!fp_sub){
22 printf("%s\n",dlerror());
23 }
24 func_t fp_mul = (func_t)dlsym(handle,"mul");
25 if(!fp_mul){
26 printf("%s\n",dlerror());
27 }
28 func_t fp_div = (func_t)dlsym(handle,"div");
29 if(!fp_div){
30 printf("%s\n",dlerror());
31 }
32
33 printf("a + b = %d\n",fp_add(a,b));
34 printf("a - b = %d\n",fp_sub(a,b));
35 printf("a * b = %d\n",fp_mul(a,b));
36 printf("a / b = %d\n",fp_div(a,b));
37
38
39 if(dlclose(handle)){
40 printf("%s\n",dlerror());
41 }
42 return 0;
43 }
再次编译并执行执行测试函数:
Desktop Linraffe$ gcc -lmath MyDlTest.c -o MyDlTest
Desktop Linraffe$ MyDlTest
a + b = 15
a - b = 1024
a * b = 36
a / b = 4
如果我们故意将源代码中的库名写错,编译后执行的效果为:
Desktop Linraffe$ MyDlTest
dlopen(libmth.so, 1): image not found
如果我们故意将源代码中的函数名写错,编译后执行的效果为:
Desktop Linraffe$ MyDlTest
dlsym(0x7f9427d00000, adc): symbol not found
以上是动态库以及动态库函数动态加载相关笔记