什么是库?
库,简单来说就是现有的,成熟的代码;
就比如我们使用的C
语言标准库,我们经常使用输入scanf
和输出printf
,都是库里面给我们实现好的,我们可以直接进行服用。
库呢又分为静态库和动态库,在Linux
中静态库文件后缀.a
,动态库文件后缀.so
;在Windows
中静态库文件后缀.lib
,动态库文件后缀.dll
。
这里注意一下库的命名规则:
库的命名都是以
lib
开头,.a/.so
为后缀;去掉前缀lib
和后缀.a/.so
剩下的部分才是库的名字;例如
C
标准库libc.so
,去掉前缀和后缀,c
就是库的名字。
动静态链接
在之前我们知道gcc/g++
在编译时默认使用动态链接,若想要进行静态链接就要带-static
选项;
- 静态链接,本质上就是程序在编译链接时,将静态库的内容链接到可执行文件中,这样可执行程序在执行时就不会再依赖库;但是静态链接的可执行文件都比较大。
- 动态链接:本质上就是程序在编译链接时,在可执行文件和动态库之间建立某种关联,这样可执行程序在执行时机会依赖动态库。
静态库
库分为静态库和动态库,那什么是静态库呢?
静态库简单来说就是所有.o
文件的归档文件,也就是说静态库就是将所有的.o
文件合并在一起。
这样在链接形成可执行程序时,将静态库和.o
文件再合并在一起形成可执行文件。
静态库的制作
这里提供两份源文件代码mystdio.c
和mystring.c
来制作库
//mystdio.c
#include "mystdio.h"
MYFILE* BuyFile(int fd, int flag)
{
MYFILE* myfile = (MYFILE*)malloc(sizeof(MYFILE));
myfile->fileno = fd;
myfile->flag = flag;
myfile->bufflen = 0;
myfile->flush_buff = LINE_FLUSH;
//初始化缓冲区
memset(myfile->outbuff, 0, sizeof(myfile->outbuff));
return myfile;
}
MYFILE* MyOpen(const char* pathname, const char* mode)
{
//确定文件的打开方式
int fd = -1;
int flag = 0;
if(strcmp(mode, "w") == 0)
{
flag = O_CREAT | O_WRONLY | O_TRUNC;
fd = open(pathname, flag, 0666);
}
else if(strcmp(mode, "a") == 0)
{
flag = O_CREAT | O_WRONLY | O_APPEND;
fd = open(pathname, flag, 0666);
}
else if(strcmp(mode, "r"))
{
flag = O_RDONLY;
fd = open(pathname, flag);
}
else{
//???
}
if(fd < 0) return NULL;//打开文件失败
return BuyFile(fd,flag);
}
void MyClose(MYFILE* file)
{
if(file == NULL) return;
if(file->fileno < 0) return;
MyFlush(file);
close(file->fileno);
free(file);
}
int MyWrite(MYFILE* file, void* str, int len)
{
//将数据拷贝到缓冲区当中
//int n = 0;
if(file->bufflen + len > MAX)
{
//n = MAX - file->bufflen;
//memcpy(file->outbuff + file->bufflen, str, n);
//MyFlush(file);
MyFlush(file);
}
//memcpy(file->outbuff + file->bufflen,(void*)((char*)str + n), strlen((char*)str) - n);
memcpy(file->outbuff + file->bufflen,str,len);
file->bufflen += len;
if(file->flush_buff & NONE_FLUSH)
MyFlush(file);
else if((file->flush_buff & LINE_FLUSH) && (file->outbuff[file->bufflen-1] == '\n' || file->bufflen == MAX))
MyFlush(file);
else if((file->flush_buff & FULL_FLUSH) && (file->bufflen == MAX))
MyFlush(file);
return 0;
}
void MyFlush(MYFILE* file)
{
if(file->bufflen <= 0) return;
write(file->fileno, file->outbuff, file->bufflen);
file->bufflen = 0;
fsync(file->fileno);
}
//mystring.c
#include "mystring.h"
int my_strlen(const char* str)
{
const char* s = str;
while(*s != '\0')
s++;
return s - str;
}
头文件mystdio.h
和mystring.h
//mystdio.h
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX 10//缓冲区大小
#define NONE_FLUSH 001 //0001
#define LINE_FLUSH 002 //0010
#define FULL_FLUSH 004 //0100
typedef struct IO_FILE{
int fileno;//文件描述符
int flag;
char outbuff[MAX]; //缓冲区
int bufflen; //缓冲区内容长度
int flush_buff;
}MYFILE;
MYFILE* MyOpen(const char* pathname, const char* mode);
void MyClose(MYFILE* file);
int MyWrite(MYFILE* file, void* str, int len);
void MyFlush(MYFILE* file);
//mystring.h
int my_strlen(const char* str);
静态库是如何生成的呢?
静态库是.o
文件的归档文件,所以我们在制作库时,就要现将所有的.c
文件编译形成.o
文件
有了.o
文件,现在就要对这些.o
文件进行归档形成静态库;
这里就要使用指令ar -rc
(其中ar
是gnu
归档工具,-rc
表示replace
和create
)
静态库的使用
了解了静态库是如何制作的,那我们如何去使用静态库呢?
站在一个库的使用者的角度,我们拿到一个库时,我们并不知道这个库里都实现了哪些方法;我们就要参考所有的头文件。
所以我们就可以把头文件看做库的使用手册,在头文件中记录了库中实现的方法。
现在我们获得了静态库libmyc.a
和头文件mystdio.h
和mystring.h
我们通过查看头文件,知道了库libmyc.a
实现了哪些方法,实现了test.c
中使用了libmyc.a
中的方法
#include "mystdio.h"
#include "mystring.h"
int main()
{
MYFILE* myfile = MyOpen("log.txt","w");
if(myfile == NULL) return -1;
const char* str = "abc-abc\n";
MyWrite(myfile, (void*)str, my_strlen(str));
MyWrite(myfile, (void*)str, my_strlen(str));
MyClose(myfile);
printf("%d\n",my_strlen(str));
return 0;
}
这里我们直接编译test.c
:
可以看到,存在链接时报错,找不到这些方法;这是因为gcc
默认情况下只会去链接C
标准库,如果想要去链接第三方库,就要带-l
选项指明要链接的库。
gcc test.c -l库名
但是我们可以看到,-lmyc
指明了要链接哪一个库,却找不到这个库;
这是因为gcc
只会在指定路径下去寻找库,而我们要链接的库myc
在当前路径下,并不在系统的指定路径下;
所以我们要使用gcc
的-L
选项来指明我们要链接的库的路径
gcc test.c -l库名 -L库的路径
如上图所示,我们要链接第三方库,带-l
选项指明库的名称;如果要链接的库博主系统路径下,带-L
选项指明库的路径。
补充:gcc -I
选项
在上述操作中,我们的头文件都在当前路径下,如果头文件不在当前路径下呢?
我们把静态库和头文件分别放在./bin/lib
和./bin/include
路径下;
在编译时gcc
在当前路径下找不到头文件,就会报错;
解决方法:
- 在
gcc
编译时带-I
选项,指明头文件的路径。 - 在源文件引用头文件时,指明路径;
#include "./bin/include/mystdio.h"
动态库
动态库:程序在运行时才会去链接动态库的代码,多个程序可以共享;
一个可执行文件和动态库链接仅仅包含它用到的函数入口地址的一个表。
可执行文件在开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中拷贝到内存中;这一过程称为动态链接
动态库可以被多个程序共享,所以动态链接的可执行文件更下,节省了磁盘空间。
动态库的制作
我们知道了静态库是.o
的归档文件,那动态库呢?
动态库又是如何生成的呢?
还是上述的代码mystd.c
、mystring.c
、mystdio.h
和mystring.h
;
首先生成动态库,也是要先将所有的.c
文件编译形成.o
文件,与生成静态库不同的是,生成动态库在编译形成.o
文件是需要带-fPIC
选项,产生位置无关码。
其次,就是将这些.o
文件形成动态库,这里使用的是gcc
,-shared
选项
gcc -o libmyc.so *.o -shared
动态库的使用
动态库的使用和静态库使用,可以说一模一样的了;
这里就直接演示使用了:
这里我们发现一个问题,我们gcc
链接libmyc.so
库,编译链接形成了可执行程序a.out
,在运行时它找不到libmyc.so
库?
通过ldd
查看a.out
可执行程序依赖的库,可以发现确实找不到libmyc.so
库。
这是为什么呢?我们在gcc
编译时,使用-L
选项不是指明libmyc.so
的路径了吗?
这是因为我们
gcc
编译是-L
选项指明libmyc
的路径,这是告诉gcc
我们要链接的库在哪,但是系统并不知道我们的库在哪里;因为这里是动态链接,在可执行程序执行时,系统就会去找库
libmyc.so
,就会发现系统找不到这个库。
运行时搜索路径
那我们知道了动态链接我们自己的库,在可执行程序运行时,系统找不到我们的库,那如何解决这一问题呢?
这里解决方案有很多,我们一一来看:
首先,我们要知道,系统为什么找不到我们自己的库,却可以找到C
语言标准库?
因为我们C
语言标准库在系统的指定目录下,可执行程序在运行时,系统会在指定路径下去寻找,所以C
语言标准库就可以被系统找到。
1. 将我们的库拷贝到系统指定路径下,系统指定路径一般指/usr/lib
、/usr/local/lib
、/lib64
2. 在系统指定文件中建立软链接
这里我们库比较小,如果我们的库比较大,拷贝到系统指定路径下很不现实;
所以我们就可以在系统指定路径下建立同名软链接。
3. 更改环境变量LD_LIBRARY_PATH
上面两种方法,都是将我们的库放入(拷贝/软链接)系统指定文件中;
我们还可以通过修改环境变量LD_LIBRARY_PATH
,让我们的库能够被系统找到
这里,博主自己的系统配置过vim
,没有配置的该环境变量可能就是空了
我们可以修改这个环境变量,把我们库的路径加上去,这样系统就可以找到我们的库libmyc.so
了。
4. ldconfig
配置
除了上述三种方法之外呢,我们还可以进行配置/etc/ld.so.conf.d/
,并更新ldconfig
;
这样系统也可以找到我们的库libmyc.so
。
本篇文章的大致内容到这里就结束了,感谢各位大佬的支持
简单总结:
静态库制作:
ar -rc
将所有.o
位置归档。动态库制作:
gcc
的-shared
选项,将所有.o
文件形成动态库库的使用:
gcc
中-l
指定链接某些库,-L
指明要链接库所在的路径,-I
指明头文件所在的路径可执行程序在运行时,系统找到我们自己库的方法:将我们的库(拷贝/创建同名软链接)在系统指定路径中、修改环境变量
LD_LIBRARY_PATH
、配置/etc/ld.so.conf.d/
中的文件,并更新ldconfig
。