链接库其实就是一个压缩包,他是把一个或者多个.o
的二进制文件打包,.o
文件是二进制文件,链接库自然也是二进制文件,这意味着我们不能通过链接库文件知道内部的具体实现,而是只有可供我们调用的外部接口,这也起到一定的安全措施,防止代码外泄。
在进行库的生成与使用之前,先把下面需要用到的代码贴出来,一共有三个文件,实现归并排序
main.c
#include"mergeSort.h"
int main(int argc, char *argv[]) {
int arr[ARRLEN];
srand(time(NULL));
for(int i=0;i<ARRLEN;i++) {
arr[i] = rand()%10000;
}
puts("原始数组为:");
printIntArr(arr, ARRLEN);
puts("归并排序后的数组为:");
mergeSort(arr, ARRLEN);
printIntArr(arr, ARRLEN);
}
mergeSort.c
#include"mergeSort.h"
//输出一个数组
void printIntArr(int arr[], size_t len) {
for(int i=0;i<len;i++) {
printf("%d ",arr[i]);
}
puts("");
}
void mergeSort(int arr[], int len) {
if(len == 1) {//长度为 1 ,直接返回
return;
}
else {//递归
//先对两个子序列排序
mergeSort(arr, len/2);
mergeSort(arr+len/2 , len-len/2);
//排完序后再合并
int tmp[len];//存储结果的临时数组
int p1 = 0;//指向第一个数组的指针
int p2 = 0;//指向第二个数组的指针
int index = 0;//存放临时数组数据的下标
while(p1<len/2 && p2 < len-len/2) {
if(arr[p1] == arr[len/2+p2]) {
tmp[index++] = arr[p1++];
tmp[index++] = arr[len/2+p2++];
}
else if(arr[p1] > arr[len/2+p2]) {
tmp[index++] = arr[len/2+p2++];
}
else if(arr[p1] < arr[len/2+p2]) {
tmp[index++] = arr[p1++];
}
}
//将剩余元素存入tmp数组
while(p1<len/2) {
tmp[index++] = arr[p1++];
}
while(p2<len-len/2) {
tmp[index++] = arr[len/2+p2++];
}
//最后将临时数组的数据替换原数组中的值
for(int i=0;i<len;i++) {
arr[i] = tmp[i];
}
}
}
mergeSort.h
#ifndef MERGESORT_H
#define MERGESORT_H
#include
#include
#include
#include
#define ARRLEN 10
void printIntArr(int arr[], size_t len);
void mergeSort(int arr[], int len);
#endif
分布方式如下:
main.c mergeSort.c headers/mergeSort.h
格式如下
gcc -shared -fPIC -o libxxx.so xxx.c yyy.c zzz.c
把xxx.c yyy.c zzz.c(等其他C语言源文件列表)
编译生成动态库,名叫libxxx.so
注意:动态库的命名一般都是以lib开头,以.so结尾
-shared : 生成动态库(共享库)
-fPIC : 与位置无关
-o : 指定要生成的动态库叫什么名字
对于我们上面的代码,生成动态库的指令为:gcc -shared -fPIC mergeSort.c -Iheaders -o libmerge.so
这样就生成了名为libmerge.so的动态库
生成动态库以后就可以直接使用动态库和头文件对main.c文件进行编译,我们甚至可以直接删除mergerSort.c这个文件。
gcc main.c -o main —> 报错
错误原因:因为没有指定库路径
指定头文件路径和库文件路径
-I : 指定头文件的搜索路径,可以接多个 -I
-L : 指定库文件的搜索路径,可以接多个 -L
-l : 指定库的名字(去掉lib和.so之后的名字),可以接多个 -l
使用gcc main.c -Iheaders -L./ -lmerge -o main1
,其中-Iheaders
指定头文件路径,-I
和headers
中间可以有空格也可以没有
-L./
指定库文件路径 -lmerge
指定依赖的库文件,其中merge是库文件的名字(动态链接库名字前面去点lib,后面去掉.so),然后 -o main
是指定生成可执行文件的名称。然后运行./main
就可以运行程序了,可以看到我们编译main.c不需要mergeSort.c文件了,可以通过动态链接库直接生成可执行文件并执行。
我们如果把mergeSort.h和libmerge.so发送给别人,别人就能直接通过头文件中的函数描述直接编写main.c文件并调用相关函数,然后和libmerge.so链接生成可执行文件,他不知道你是怎么实现函数功能的,但是他知道调用什么函数,传入什么参数就能达到什么样的效果。
看起来好像动态链接库已经完了,不妨试试下面的操作,先通过mkdir libs
创建一个存放库文件的目录,然后mv libmerge.so libs/
将动态库文件移动一个地方,然后我们再通过./main
进行执行,这时候就会发现报错了。
./main1: error while loading shared libraries: libmerge.so: cannot open shared object file: No such file or directory
无法找到库文件,其实编译和运行都需要库文件,如果库文件就在当前路径下,运行时会自动搜索当前路径下是否有库文件,但是编译不一样,编译时哪怕在当前路径下,也需要显式的指定路径,和头文件 有点类似,如果头文件就在当前目录下,则无需指定头文件路径,否则的话就需要通过-I来指定头文件搜索路径。
那碰到这种情况怎么办呢?
将库文件复制到系统指定的搜索路径下面
例如sudo cp libs/libmerge.so /lib/
sudo cp libs/libmerge.so /usr/lib/
将动态链接库的路径放到程序搜索路径中,可以通过将库的路径添加到LD_LIBRARY_PATH环境变量中实现。
例如:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/hgfs/code/12_8_2022/test/libs
指令的意思就是在原来LD_LIBRARY_PATH的后面追加:/mnt/hgfs/code/12_8_2022/test/libs
而这就是我的库文件夹路径。
当然通过这种方式的环境变量会在你重新登录时失效,如果不想每次都设置的话可以将这条指令添加到用户的配置文件中去,像我的ubuntu就是家目录下的.bashrc
文件。
先将源代码编译生成.o的目标文件
gcc -c mergeSort.c -Iheaders
使用ar命令把所有的.o文件生成一个静态库
ar -rc libmerge.a mergeSort.o
注意这里的目标名字直接跟在-rc后面而不需要也不能够使用-o指定文件名。
gcc main.c -Iheaders -static -L./ -lmerge -o main2
其中-static是指定在同路径下如果同时存在动态库和静态库的话使用静态库,因为默认是使用动态库的。
然后使用./main2
运行程序,接下来继续把库文件移动到libs下面,mv libmerge.a libs/
,然后继续运行main程序,发现和动态库不一样,这里并不会提示找不到库文件。
就从上面我们发现的运行时需要知道库文件的路径和不需要知道库文件的路径来讲解他们之间的区别和优缺点。
为什么动态库在运行时也需要知道库文件的路径呢?因为他在编译的时候并没有将库文件中的代码复制到可执行文件中,而只是标记了库文件中函数函数与变量对应的位置,等到需要使用的时候就去库文件中对应的位置执行。而静态库是在编译的时候就把需要使用的代码copy到可执行文件中的对应的位置。所以相对来说,使用动态链接库的可执行文件更小(程序中没有大量重复冗余的代码),更灵活(库文件修改后无需再编译就能达到效果),现在的主流就是使用动态库,而静态库中有大量的重复的代码,但是他执行时无须在其他文件中寻找我需要的代码,所以更快速。
基于上面的灵活性我们再做一个小实验。
在mergeSort.c最后再输出一个hello world。然后重新编译动态库和静态库文件,然后运行原来的可执行文件,会发现原来链接动态库的文件输出改变了 ,而静态库的并没有,需要再次编译后才能改变。