前言
为了调用者使用的方便,一般并不会直接提供对应的.c文件或者.o文件,而是根据具体的功能模块,将对应的多个.o文件打包一个/多个库文件,给调用者提供库文件和头文件即可,系统默认提供了标准库还有一些其他的库文件,用户也可以自定义函数库,那么根据库连接的方式不同主要将函数库分为共享库(动态库)和静态库两种。
静态库
目录
1.概述
2.创建静态库以及使用静态库
概述
静态库:也叫做归档文件,以.a结尾。
使用静态库的时候,编译器在编译过程中直接将代码嵌入到目标文件中,所以一旦完成编译,那么静态库可以不需要了。
静态库的优点:不需要跳转,执行效率相对比较高。
静态库的缺点:目标文件相对比较大,并且后期的修改以及维护不方便。
静态库文件命名规范:lib(库名).a 比如:libadd.a
库文件名和库名是不同的概念,库名没前缀和后缀
创建静态库
1.创建头文件(add.h)
#ifndef __HH_ADD_H__
#define __HH_ADD_H__
int add(int,int);
#endif
2.创建源程序代码文件(add.c)
#include "add.h"
int add(int num1,int num2){
return num1+num2;
}
3.编译源程序 生成目标文件 (add.o)
gcc -c add.c
4.使用ar打包工具生成静态库
ar -r 静态库文件 目标文件
ar -r libadd.a add.o
5.编写测试文件(main.c)
#include <stdio.h>
#include "add.h"
int main(){
printf("1+1=%d",add(1,1));
return 0;
}
6.使用静态库文件
第一种使用方式:直接链接
gcc 目标文件(源代码文件) 静态库文件
gcc main.c libadd.a
lurongshuang@ubuntu:~/work/work1020$ gcc main.c libadd.a
lurongshuang@ubuntu:~/work/work1020$ ./a.out
1+1=2
lurongshuang@ubuntu:~/work/work1020$
第二种使用方式:使用编译选项进行链接 (这种方式使用较多)
gcc/cc 目标文件(源代码文件) -l 库名 -L 库文件所在的路径
gcc main.c -l add -L .
lurongshuang@ubuntu:~/work/work1020$ rm a.out
lurongshuang@ubuntu:~/work/work1020$ gcc main.c -l add -L.
lurongshuang@ubuntu:~/work/work1020$ ./a.out
1+1=2
lurongshuang@ubuntu:~/work/work1020$
第三种使用方式:配置环境变量 LIBRARY_PATH
export LIBRARY_PATH=路径
gcc 目标文件(源代码文件) -l 库名
lurongshuang@ubuntu:~/work/work1020$ gcc main.c -l add
/usr/bin/ld: 找不到 -ladd
collect2: error: ld returned 1 exit status
lurongshuang@ubuntu:~/work/work1020$ export LIBRARY_PATH=$LIBRARY_PATH:.
lurongshuang@ubuntu:~/work/work1020$ gcc main.c -l add
lurongshuang@ubuntu:~/work/work1020$ ./a.out
1+1=2
lurongshuang@ubuntu:~/work/work1020$
动态库
目录
1.概述
2.创建动态库以及使用动态库
概述
动态库也叫做共享对象库、共享库等,以.so结尾。
使用动态库时,编译器并不直接在编译的时候将代码嵌入到目标文件中,而是等到运行时调用相应的函数,才加载代码。
动态库的优点:相比静态库 目标文件小,后期的修改维护方便。
动态库的缺点:因为运行时需要跳转,效率会低,并且不能脱离共享库文件。
动态态库文件命名规范:lib(库名).so 比如:libadd.so
库文件名和库名是不同的概念,库名没前缀和后缀
创建动态库以及使用静态库
1.创建头文件(add.h)
#ifndef __HH_ADD_H__
#define __HH_ADD_H__
int add(int,int);
#endif
2.创建源程序代码文件(add.c)
#include "add.h"
int add(int num1,int num2){
return num1+num2;
}
3.编译源程序 生成目标文件 (add.o)
gcc -c -fpic 源代码文件
gcc -c -fpic add.c
lurongshuang@ubuntu:~/work/work1020$ gcc -c -fpic add.c
lurongshuang@ubuntu:~/work/work1020$ ls
add.c add.h add.o main.c
lurongshuang@ubuntu:~/work/work1020$
fpic的目的是什么?(赵晨斌讲师的理解)
共享库可能会被不同的进程加载到不同的位置上,如果共享库中的指令使用了绝对地址、外部模块地址,那么在共享库被加载时就必须根据相关模块的加载位置对这个地址做调整,也就是修改这些地址,让它在对应进程中能正确访问,而被修改到的段就不能实现多进程共享一份物理内存,它们在每个进程中都必须有一份物理内存的拷贝。fPIC指令就是为了让使用到同一个共享对象的多个进程能尽可能多的共享物理内存,它背后把那些涉及到绝对地址、外部模块地址访问的地方都抽离出来,保证代码段的内容可以多进程相同,实现共享。
4.生成动态库
gcc/cc -shared 目标文件(xxx.o) -o lib库名.so
gcc -shared add.o -o libadd.so
lurongshuang@ubuntu:~/work/work1020$ gcc -shared add.o -o libadd.so
lurongshuang@ubuntu:~/work/work1020$ ls
add.c add.h add.o libadd.so main.c
lurongshuang@ubuntu:~/work/work1020$
5.编写测试文件(main.c)
#include <stdio.h>
#include "add.h"
int main(){
printf("1+1=%d",add(1,1));
return 0;
}
6.使用动态库文件
动态链接两种不同的方式:
1:隐式链接
隐式链接在编译/链接阶段完成,由编译系统根据动态库的头文件和库文件进行编译和链接,从而确定待调用的函数原形和地址。
2.显式链接
显式链接则是利用API函数实现加载和卸载共享库,获取带调用函数地址,获取错误信息等功能。(动态加载共享库)
第一种使用方式:直接链接 (隐式链接)
gcc 目标文件 动态库文件
gcc main.o libadd.so
lurongshuang@ubuntu:~/work/work1020$ ls
add.c add.h add.o libadd.so main.c main.o
lurongshuang@ubuntu:~/work/work1020$ gcc main.o libadd.so
lurongshuang@ubuntu:~/work/work1020$ ./a.out
./a.out: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
----------------------------------注意下面这段 --------------------------------
直接运行是不能成功的 需要配置 LD_LIBRARY_PATH环境变量
lurongshuang@ubuntu:~/work/work1020$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
lurongshuang@ubuntu:~/work/work1020$ ./a.out
1+1=2
lurongshuang@ubuntu:~/work/work1020$
----------------------------------注意上面这段 --------------------------------
第二种使用方式:使用编译选项进行链接(掌握) (隐式链接)
gcc/cc main.o -l 库名 -L 库文件所在的路径
gcc main.o -l add -L .
lurongshuang@ubuntu:~/work/work1020$ ls
add.c add.h add.o a.out libadd.so main.c main.o
lurongshuang@ubuntu:~/work/work1020$ rm a.out
lurongshuang@ubuntu:~/work/work1020$ gcc main.o -l add -L .
lurongshuang@ubuntu:~/work/work1020$ ./a.out
1+1=2
lurongshuang@ubuntu:~/work/work1020$
第三种使用方式:配置环境变量LIBRARY_PATH(隐式链接)
export LIBRARY_PATH=路径
gcc 目标文件 -l 库名
export LIBRARY_PATH=$LIBRARY_PATH:.
gcc main.o -l add
lurongshuang@ubuntu:~/work/work1020$ ls
add.c add.h add.o libadd.so main.c main.o
lurongshuang@ubuntu:~/work/work1020$ export LIBRARY_PATH=$LIBRARY_PATH:.
lurongshuang@ubuntu:~/work/work1020$ gcc main.o -l add
lurongshuang@ubuntu:~/work/work1020$ ./a.out
1+1=2
lurongshuang@ubuntu:~/work/work1020$
第四种使用方式:使用函数动态加载(显式加载)
void *dlopen(const char *filename,int flag);
需要引入头文件 dlfcn.h
第一个参数:字符串形式的共享库文件名
第二个参数:标志 RTLD_LAZY -懒加载 只适用于函数。只有在函数被执行的时候,才确定函数的地址。函数不执行,不加载。RTLD_NOW -立即加载 在dlopen返回之前,动态库的符号就已经确定了地址
返回值:通用类型指针, 成功返回句柄,暂时理解为首地址,失败返回NULL
函数功能:主要用于打开和加载共享库文件
char *dlerror(void);
函数功能:主要用于获取dlopen等函数调用过程发生的最近一个错误的详细信息,返回NULL则表示没有错误发生
void *dlsym(void *handle,const char *symbol);
第一个参数:句柄,也就是dlopen函数的返回值
第二个参数:字符串形式的符号,表示函数名
返回值:成功返回函数在内存中的地址,失败返回NULL
函数功能: 主要用于根据句柄和函数名获取在内存中的地址
int dlclose(void *handle);
函数功能: 主要用于关闭参数handle所指定的共享库,成功返回0,失败返回非0,当共享库不再被任何程序使用时,则回收共享库所占用的内存空间
结合以上罗列函数,综合演示如下:
#include <stdio.h>
#include <dlfcn.h>
//简单写一下 忽略部分语法错误
int main(){
int (*add)(int,int);
void *handle = dlopen("./libadd.so",RTLD_NOW);
if(handle==NULL) {
char * str = dlerror();
if(str!=NULL) {
printf("出现问题:%s",str);
}
return -1;
}
add = dlsym(handle,"add");
if(add==NULL) {
char * str = dlerror();
if(str!=NULL) {
printf("出现问题:%s",str);
}
dlclose(handle);
return -1;
}
printf("1+1=%d\n",add(1,1));
dlclose(handle);
return 0;
}
补充
列举一下生成库文件过程中 2个辅助的命令
1.查看目标文件所依赖的动态库
ldd XXX
例如:lld a.out
2.查看目标文件所包含的静态库文件、动态库文件、函数名称、变量等。
nm XXX
例如:nm libadd.o
网友评论