linuxlinux基础IO七静态库的制作与使用
目录
【linux】linux基础IO(七)静态库的制作与使用
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
前言
【linux】linux基础IO(六)软硬链接(软链接,硬链接)——书接上文 ,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍——【linux】linux基础IO(七)静态库的制作与使用
一、回顾知识
- 在之前的文章中,小编已经讲过动静态库的一些基础的认识,读者友友可以点击后方蓝字链接进行学习第四点关于动静态库的讲解
- 在本文中,小编会将一些基本的认识,对本文的讲解有帮助的动静态库的一些知识进行二次讲解
- 动静态库的命名是有特殊规则的,动态库又叫做共享库,其中动静态库的后缀不同,但是都是以lib开头的,静态库的后缀是.a,动态库的后缀是.so,并且动静态库的真实名称实际上是掐头去尾,即去掉开头的lib,去掉后缀,中间剩余的就是动静态库的真实名称
libXXX.a(静态库)
libXXX.so(动态库)
- 系统的库的路径是 /lib64
- ldd 可执行程序,可以查看一个可执行程序运行所需要的共享库
- file 可执行程序,可以查看一个可执行程序的链接方式
- 为什么需要动静态库?假设我们想将我们的方法提供给其它人使用,有两种方式,第一种方式是直接将源文件给其他人进行使用,但是我并不想要将具体方法的实现给其他人,所以有了第二种方式,即想办法将我们的源文件打包成库给其他人使用,即头文件.h + 库的方式
- 那么接下来小编将会带领大家站在库的制作者的角度,自己去实现一个简单的静态库,并且接下来站在库的使用者的角度,去实际使用一下静态库,同理动态库的讲解也是和静态库一样的流程
二、静态库的制作
- 小编今天要带领大家实现的简单的静态库其实很简单,即这个静态库可以提供加减乘除方法,相信读者友友编写加减乘除方法的头文件以及源文件应该是可以手到擒来的,将加减乘除方法制作成库供其他人使用,并且提供一个全局变量的一个错误码myerrno,当出现除零错误的时候就可以返回-1表示出错,并且设置具体的出错码表示出错的原因
- 那么就要先有两个文件,即头文件mymath.h和源文件mymath.c,这两个文件是制作静态库的基础
//mymath.h
#pragma once //防止头文件被重复包含
#include <stdio.h>
extern int myerrno;//声明错误码myerrno,这样在每一个.c文件中都可以定义使用myerrno变量不互相影响
int Add(int x, int y);
int Sub(int x, int y);
int Mul(int x, int y);
int Div(int x, int y);
//mymath.c
#include "mymath.h"
int myerrno = 0;//错误码初始化为0,表示没有错误
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
if(y == 0)//判断除零错误
{
myerrno = 1;//设置错误码表示出错的原因
return -1; //返回-1表示出错了
}
return x / y;
}
- 其实无论是静态库还是动态库,给用户提供的永远都是头文件+库,拿c语言的库中,给我们提供的是头文件,并且我们使用诸如vs这种编程工具的时候,vs安装已经将c语言库放在了系统的特定路径下这样在使用的时候就可以找到对应的库进行使用,为什么c语言库要提供头文件,因为头文件其实就是库方法的一份说明,几个参数,函数名是什么如何调用,包含什么方法等
- 所以为用户提供一个库一定要提供对应的头文件,光有头文件还不行,同样的,还应该提供对应的方法,这个方法由于我们不想让用户看到方法如何实现,所以这个方法一定不是直接将源文件的实现给用户,这个方法一定是进行编译汇编之后形成的二进制的.o文件,将.o文件提供给的用户,用户在自己的main函数中包含方法的头文件,并且将包含有main函数的文件进行预处理,编译,汇编形成.o文件和我们提供的方法的.o文件进行链接,进而就可以形成可执行程序
- 所以我们提供的库中,无论是静态库还是动态库都应该包含有头文件,以及.o文件,由于我们可能会提供多个方法,被包含在不同的头文件中,例如c语言中的#include <stdio.h>,#include<stdlib.h>,#include<string.h>等等,即库的制作者可能会提供不同头文件对应的方法编译汇编形成的多份.o文件,所以静态库或者动态库实际上就是将提供的.o文件进行打包,给用户提供的库应该是一个目录文件,包含有头文件目录(放多份头文件)和库目录(放多份库文件)
- 那么同样的小编带领大家实现的简单的静态库同样也要可以形成.o文件,并且.o文件也应该进行打包形成库,如何打包,即使用ar归档工具,ar是gnu归档工具专门用来进行静态库的打包,静态库需要以lib开头,以.a进行结尾,同时在打包前应该先形成对应的.o文件,这时候我们使用makefile自动化构建工具即可,同时将头文件.h以及.a库文件形成用于发送给用户的库也可以使用makefile进行构建
lib=libmymath.a //定义lib变量,其实代表的是libmymath.a后面便于进行修改
$(lib):mymath.o //$(lib)的意思是libmymath.a, libmymath.a作为目标文件的依赖方法是mymath.o
ar -rc $@ $^ //使用ar归档工具, 将mymath.o打包成libmymath.a
mymath.o:mymath.c
gcc -c $^ //gcc -c $^不使用-o进行命名的时候, 进行汇编后的结果命名会形成mymath.c名称的同名.o后缀的名称,即mymath.o
.PHONY:clean //.PHONY伪目标clean既然可以执行rm指令,那么同样也可以执行其它指令
clean:
rm -rf *.a *.o lib
.PHONY:output //那么我们同样也可以执行其它指令
output:
mkdir -p lib/include //注意,这里不要将这里的lib跟第一行的lib混淆,第一行的lib只有使用
mkdir -p lib/mymathlib //$(lib)的时候才代表libmymath.a,这里的lib仅仅是一个名称,代表的就是lib,递归式建立目录
cp -rf *.h lib/include //将.h为后缀的头文件放到指定头文件目录路径下
cp -rf *.a lib/mymathlib //将.a为后缀的静态库放到指定库目录路径下
- 此时我们就可以使用自动化构建工具makefile去构建我们的包含静态库libmymath.a以及头文件的提供给用户的lib目录库了,此时我们的库就制作完成了
三、静态库的使用
- 此时我们已经有了一个可以发送给用户的包含有静态库以及头文件的lib库了,现在我们站在用户的角度去使用一下小编提供的包含有静态库以及头文件的lib库
- 那么这里小编就简单的在一个当前目录下,新建一个test目录模拟用户,使用mv将lib库移动到test目录下,模拟将lib库发送给用户,此时进入test目录,用户查看到lib库了,此时我们作为一个用户就开始使用了
- 首先,作为用户,我们要有main函数才能进行编写代码,使用lib库的方法,所以我们先创建一个main.c文件,使用vim编辑器,包头文件#include “mymath.h”,调用Add方法进行使用
#include "mymath.h"
int main()
{
printf("1 + 1 = %d\n", Add(1, 1));
return 0;
}
- 如果想要运行main.c首先就要使用gcc编译形成可执行,那么我们使用gcc之后结果缺是下面这样,这究竟是什么原因?明显的,这个错误信息提示我们找不到头文件mymath.h,你逗我呢,gcc?明明这个mymath.h这个头文件就在当前目录的lib目录里面,你却告诉我找不到
- 是的,用户,我gcc确实找不到,因为我gcc要求的头文件要在和main.c的同一级目录下,不能够是在和mian.c的同级目录lib的目录下,我gcc只会现在系统的头文件路径 /usr/include下,以及当前目录路径下的同级目录进行查找,我们提供的头文件即不在系统的特定头文件/usr/include路径下,同时也不在当前目录路径的同级目录,所以自然会找不到,那么怎么办呢?
- 有办法,其中第一个办法是在mian.c源文件中直接将头文件的名称加路径直接写入,即从#include “mymath.h"变成#include “lib/include/mymath.h"但是这样就有点不好,因为我常规使用c语言的头文件的时候都是直接使用#include <stdio.h> 以及#include <stdlib.h>等等,都没有带路径,我用户要使用你这个头文件还要带路径,那么如果我用户要是使用10多个头文件,那我得多麻烦,写很多个路径,所以我用户不想这样通过头文件的名称中加入路径的方式调用头文件,那么又该如何办呢?
- 第二个方法,gcc -I 路径,即gcc添加选项I,I是大写的i,-l的意思是include,即头文件的意思,在这个选项后面跟上头文件的路径即可,这样gcc在搜索头文件的时候,就回去我们指定的路径下去搜索头文件了
- 观察上图,此时我们给gcc使用 I 选项添加上头文件的搜索路径后,此时gcc可以找到头文件#include “mymath.h"了,可是怎么又出现问题了?错误提示在mian函数中找不到Add,Add函数方法未定义,这又改如何理解,此时我们看到下面的错误提示中有一个cc5Aooqk.o
- 此时我们应该敏感起来,以.o为后缀的一定是链接错误,其实为什么我们日常在进行c/c++编写,使用c/c++库的方法的时候,使用gcc/g++进行编译形成可执行的时候没有出现过这个链接错误,因为系统中早就已经在特定的系统路径下 /lib64下早已经提前安装好了对应的c/c++的库,所以gcc/g++在进行搜索的时候,就会去指定的系统路径下搜索c/c++的库进行链接,以静态库为例,链接的时候,将main函数中用到的库函数方法从静态库中拷贝到main函数中,这样就链接完成,也不会报错
- 但是我们自己提供的第三方库并没有在这个库中,所以gcc去/lib64这个系统路径下自然也没有办法进行链接,如何解决呢?我们可以给gcc类似于头文件一样提供搜索路径,即gcc -L 库文件路径,这个-L选项中的L是lib库的意思,这样使用-L选项gcc就回去指定的路径下去搜索我们的库了
- 小编,可是上面的结果为什么还是找不到Add对应的函数实现,无法进行链接呢?gcc,我告诉了你头文件路径,告诉了你静态库的路径,让你去搜索对应的静态库你还是找不到,还是无法链接,逗我呢?是的用户,这样我确实还是找不到对应的静态库,因为你没有告诉我要链接哪一个静态库,因为当前路径下库可能有很多个库,gcc无法确定要链接哪一个。
- 那么同样的,对于头文件,我也仅仅是告诉了你gcc去哪一个路径下去找,你gcc可以找到对应的头文件,到了静态库这里告诉你gcc去哪一个路径下去找,你却找不到。是的确实找不到,因为对于头文件,其实你是告诉了我gcc要找哪一个头文件,因为你用户包的头文件就是我gcc要找的头文件,但是目前用户却没有告诉我要在你用户指定的路径下去找哪一个静态库
- 所以此时作为用户,我们不光应该使用-L选项告诉gcc去哪一个路径下去找静态库,同时还应该使用-l选项(这里的l是小写的l)告诉gcc去找哪一个静态库,以libXXX.a为例,那么静态库的真实名称是XXX,并且使用-l选项的时候,一般将要搜索的库名称和-l放在一起,即-lXXX这种形式,所以我们再使用-l选项告诉gcc去找哪一个静态库,看gcc是否可以找到静态库并链接形成可执行
- 如上,历经千辛万苦,终于是让gcc找到了对应的头文件,以及静态库进行链接形成可执行,此时我们执行可执行,运行成功,使用成功了我们提供的库函数方法
- 小编可是你这种形式gcc -I ./lib/include main.c -L ./lib/mymathlib -lmymath 形成可执行未免也太过长了,我作为用户在命令行中想要简洁一点,不想使用这种方法,有其它的方法吗?有的
- 直接将我们提供的头文件安装在系统的头文件路径/usr/include下,将我们提供的静态库安装在系统的库路径/bin64 下,由于是向系统路径中进行写入,并且我们使用的是普通用户,所以需要sudo提权才能以普通用户的身份向系统级路径下进行写入安装
- 这样就可以在命令行中不带搜索头文件以及静态库的搜索路径了,因为gcc会去系统的头文件路径/usr/include下以及系统的库路径/bin64 下去进行搜索,此时我们已经将我们提供的头文件以及静态库安装在了系统的路径下,所以gcc自然可以搜索到,但是由于系统的库路径下存在很多库,同样的gcc并不知道要搜索链接哪一个,所以对于库的名称我们仍然需要使用-l(l是小写的l)选项告诉gcc我们要在库路径下搜索链接哪一个库
- 所以我们通过拷贝安装的方式,将我们提供的头文件安装在系统的头文件路径/usr/include下,将我们提供的静态库安装在系统的库路径/bin64 下,此时通过,-l(l是小写的l)选项告诉gcc我们要在库路径下搜索链接哪一个库,就可以编译链接形成可执行程序a.out了,此时进行执行无误,但是通常来讲,我们并不会将不成熟的第三方库直接安装在系统的指定路径下,因为不成熟的第三方库可能会对系统特定的路径目录造成污染,但是对于成熟的第三方库我们可以直接安装在系统的特定路径下,所以现在由于小编编写的第三方库仅仅是一个为了配合小编讲解静态库的第三方库,并不成熟,所以下面小编将我们自己的第三方库sudo提权rm卸载一下
- 当小编卸载了第三方库之后,此时再去使用gcc main -lmymath自然而然就没有办法找到对应的头文件以及静态库了,因为此时头文件以及静态库已经不在系统的特定目录下了
- 小编小编,有没有办法可以不拷贝第三方库到系统的特定路径下,而是采用一种链接的方式,有的,我们可以通过软链接的方式将第三方库以链接的形式安装到系统的特定路径下
- 所以此时我们通过软链接的方式将第三方库以链接的形式安装到系统的特定路径下,这样同样通过,-l(l是小写的l)选项告诉gcc我们要在库路径下搜索链接哪一个库,就可以编译链接形成可执行程序a.out了,此时进行执行无误
- 同样的由于小编也将软链接文件sudo unlink卸载一下,卸载软链接之后,无法使用gcc main.c -lmymath形成可执行程序了,因为gcc进行搜索的时候,无法在系统的特定路径下找到对应的头文件以及静态库了
- 那么接下来我们测试一下除零错误处理
#include "mymath.h"
int main()
{
printf("5 / 0 = %d, errno = %d\n", Div(5, 0), myerrno);
return 0;
}
运行结果如下
奇怪,小编明明使用的是我们自己的除数Div方法,此时进行了除零错误,应该将myerrno设置为1,表示出现除零错误,可是这里为什么errno仍然是0呢?
- 观察一下右边的函数调用printf(“5 / 0 = %d, errno = %d\n”, Div(5, 0), myerrno); 函数传参是从右向左开始进行传参,errno的打印在执行Div(5, 0)函数的前面,所以在打印errno的时候,errno是被初始化为0,直接就进行打印了,此时再执行Div(5, 0),我们的Div函数方法检测到发生除零错误,返回-1,设置错误码myerrno表示具体的错误原因,此时myerrno被设置为了1,但是此时errno已经在执行Div(5, 0)前打印完了,所以此时虽然设置了错误码myerrno,但是并不会再打印errno了
- 如何调整呢?很简单,先让Div(5, 0)执行,接收Div(5, 0)的返回值ret,将ret和errno一并打印即可
#include "mymath.h"
int main()
{
int ret = Div(5, 0);
printf("5 / 0 = %d, errno = %d\n", ret, myerrno);
return 0;
}
运行结果如下
所以此时除零错误返回-1,表示结果出错,但是如果没有显示的打印出5 / 0,此时我们就无法区分这个结果-1究竟是正确结果还是出错返回的-1,所以类似的我们就提供了一个全局变量myerrno,当myerrno不为0的时候用于表征出现错误,myerrno具体是哪一个数字表示出错的原因
- 同样的c语言库中的errno也是类似于我们设计的myerrno这样的原理,errno也是c语言库提供的一个全局变量,errno为0表示没有出错,当errno不为0的时候用于表征出现错误,errno具体是哪一个数字表示出错的原因,并且我们还应该认识到库中不仅仅能够提供方法,同时也能提供其它的变量,诸如我们这里的errno全局变量,以及其他的诸如FILE流,#define宏定义,typedef的类型定义,结构体struct,联合union,枚举enum,条件编译#pragma once等等
- 所以对于静态库我们应该有下面的认知,1. 当我们使用第三方库的时候,往往必定要使用gcc -l,2. 如果系统中只提供静态库,那么gcc只能对该库进行静态链接,3. 如果系统中需要链接多个库,则gcc可以链接多个库
总结
以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!