【linux】linux基础IO(七)静态库的制作与使用

【linux】linux基础IO(七)静态库的制作与使用
小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!

目录


前言

【linux】linux基础IO(六)软硬链接(软链接,硬链接)——书接上文 详情请点击<——,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍——【linux】linux基础IO(七)静态库的制作与使用

一、回顾知识

  1. 在之前的文章中,小编已经讲过动静态库的一些基础的认识,读者友友可以点击后方蓝字链接进行学习第四点关于动静态库的讲解详情请点击<——
  2. 在本文中,小编会将一些基本的认识,对本文的讲解有帮助的动静态库的一些知识进行二次讲解
  3. 动静态库的命名是有特殊规则的,动态库又叫做共享库,其中动静态库的后缀不同,但是都是以lib开头的,静态库的后缀是.a,动态库的后缀是.so,并且动静态库的真实名称实际上是掐头去尾,即去掉开头的lib,去掉后缀,中间剩余的就是动静态库的真实名称
libXXX.a(静态库) libXXX.so(动态库) 
  1. 系统的库的路径是 /lib64
  2. ldd 可执行程序,可以查看一个可执行程序运行所需要的共享库
  3. file 可执行程序,可以查看一个可执行程序的链接方式
  4. 为什么需要动静态库?假设我们想将我们的方法提供给其它人使用,有两种方式,第一种方式是直接将源文件给其他人进行使用,但是我并不想要将具体方法的实现给其他人,所以有了第二种方式,即想办法将我们的源文件打包成库给其他人使用,即头文件.h + 库的方式
  5. 那么接下来小编将会带领大家站在库的制作者的角度,自己去实现一个简单的静态库,并且接下来站在库的使用者的角度,去实际使用一下静态库,同理动态库的讲解也是和静态库一样的流程

二、静态库的制作

  1. 小编今天要带领大家实现的简单的静态库其实很简单,即这个静态库可以提供加减乘除方法,相信读者友友编写加减乘除方法的头文件以及源文件应该是可以手到擒来的,将加减乘除方法制作成库供其他人使用,并且提供一个全局变量的一个错误码myerrno,当出现除零错误的时候就可以返回-1表示出错,并且设置具体的出错码表示出错的原因
  2. 那么就要先有两个文件,即头文件mymath.h和源文件mymath.c,这两个文件是制作静态库的基础
//mymath.h#pragmaonce //防止头文件被重复包含#include<stdio.h>externint myerrno;//声明错误码myerrno,这样在每一个.c文件中都可以定义使用myerrno变量不互相影响intAdd(int x,int y);intSub(int x,int y);intMul(int x,int y);intDiv(int x,int y);
//mymath.c#include"mymath.h"int myerrno =0;//错误码初始化为0,表示没有错误intAdd(int x,int y){return x + y;}intSub(int x,int y){return x - y;}intMul(int x,int y){return x * y;}intDiv(int x,int y){if(y ==0)//判断除零错误{ myerrno =1;//设置错误码表示出错的原因return-1;//返回-1表示出错了}return x / y;}
  1. 其实无论是静态库还是动态库,给用户提供的永远都是头文件+库,拿c语言的库中,给我们提供的是头文件,并且我们使用诸如vs这种编程工具的时候,vs安装已经将c语言库放在了系统的特定路径下这样在使用的时候就可以找到对应的库进行使用,为什么c语言库要提供头文件,因为头文件其实就是库方法的一份说明,几个参数,函数名是什么如何调用,包含什么方法等
  2. 所以为用户提供一个库一定要提供对应的头文件,光有头文件还不行,同样的,还应该提供对应的方法,这个方法由于我们不想让用户看到方法如何实现,所以这个方法一定不是直接将源文件的实现给用户,这个方法一定是进行编译汇编之后形成的二进制的.o文件,将.o文件提供给的用户,用户在自己的main函数中包含方法的头文件,并且将包含有main函数的文件进行预处理,编译,汇编形成.o文件和我们提供的方法的.o文件进行链接,进而就可以形成可执行程序
  3. 所以我们提供的库中,无论是静态库还是动态库都应该包含有头文件,以及.o文件,由于我们可能会提供多个方法,被包含在不同的头文件中,例如c语言中的#include <stdio.h>,#include<stdlib.h>,#include<string.h>等等,即库的制作者可能会提供不同头文件对应的方法编译汇编形成的多份.o文件,所以静态库或者动态库实际上就是将提供的.o文件进行打包,给用户提供的库应该是一个目录文件,包含有头文件目录(放多份头文件)和库目录(放多份库文件)
  4. 那么同样的小编带领大家实现的简单的静态库同样也要可以形成.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目录库了,此时我们的库就制作完成了

在这里插入图片描述

三、静态库的使用

  1. 那么这里小编就简单的在一个当前目录下,新建一个test目录模拟用户,使用mv将lib库移动到test目录下,模拟将lib库发送给用户,此时进入test目录,用户查看到lib库了,此时我们作为一个用户就开始使用了
  2. 首先,作为用户,我们要有main函数才能进行编写代码,使用lib库的方法,所以我们先创建一个main.c文件,使用vim编辑器,包头文件#include “mymath.h”,调用Add方法进行使用

此时我们已经有了一个可以发送给用户的包含有静态库以及头文件的lib库了,现在我们站在用户的角度去使用一下小编提供的包含有静态库以及头文件的lib库

在这里插入图片描述
#include"mymath.h"intmain(){printf("1 + 1 = %d\n",Add(1,1));return0;}
  1. 有办法,其中第一个办法是在mian.c源文件中直接将头文件的名称加路径直接写入,即从#include "mymath.h"变成#include "lib/include/mymath.h"但是这样就有点不好,因为我常规使用c语言的头文件的时候都是直接使用#include <stdio.h> 以及#include <stdlib.h>等等,都没有带路径,我用户要使用你这个头文件还要带路径,那么如果我用户要是使用10多个头文件,那我得多麻烦,写很多个路径,所以我用户不想这样通过头文件的名称中加入路径的方式调用头文件,那么又该如何办呢?
  2. 小编,可是上面的结果为什么还是找不到Add对应的函数实现,无法进行链接呢?gcc,我告诉了你头文件路径,告诉了你静态库的路径,让你去搜索对应的静态库你还是找不到,还是无法链接,逗我呢?是的用户,这样我确实还是找不到对应的静态库,因为你没有告诉我要链接哪一个静态库,因为当前路径下库可能有很多个库,gcc无法确定要链接哪一个。
  3. 如上,历经千辛万苦,终于是让gcc找到了对应的头文件,以及静态库进行链接形成可执行,此时我们执行可执行,运行成功,使用成功了我们提供的库函数方法
  4. 小编可是你这种形式gcc -I ./lib/include main.c -L ./lib/mymathlib -lmymath 形成可执行未免也太过长了,我作为用户在命令行中想要简洁一点,不想使用这种方法,有其它的方法吗?有的
  5. 直接将我们提供的头文件安装在系统的头文件路径/usr/include下,将我们提供的静态库安装在系统的库路径/bin64 下,由于是向系统路径中进行写入,并且我们使用的是普通用户,所以需要sudo提权才能以普通用户的身份向系统级路径下进行写入安装
  6. 当小编卸载了第三方库之后,此时再去使用gcc main -lmymath自然而然就没有办法找到对应的头文件以及静态库了,因为此时头文件以及静态库已经不在系统的特定目录下了
  7. 所以此时我们通过软链接的方式将第三方库以链接的形式安装到系统的特定路径下,这样同样通过,-l(l是小写的l)选项告诉gcc我们要在库路径下搜索链接哪一个库,就可以编译链接形成可执行程序a.out了,此时进行执行无误
  8. 那么接下来我们测试一下除零错误处理

同样的由于小编也将软链接文件sudo unlink卸载一下,卸载软链接之后,无法使用gcc main.c -lmymath形成可执行程序了,因为gcc进行搜索的时候,无法在系统的特定路径下找到对应的头文件以及静态库了

在这里插入图片描述

小编小编,有没有办法可以不拷贝第三方库到系统的特定路径下,而是采用一种链接的方式,有的,我们可以通过软链接的方式将第三方库以链接的形式安装到系统的特定路径下

在这里插入图片描述

所以我们通过拷贝安装的方式,将我们提供的头文件安装在系统的头文件路径/usr/include下,将我们提供的静态库安装在系统的库路径/bin64 下,此时通过,-l(l是小写的l)选项告诉gcc我们要在库路径下搜索链接哪一个库,就可以编译链接形成可执行程序a.out了,此时进行执行无误,但是通常来讲,我们并不会将不成熟的第三方库直接安装在系统的指定路径下,因为不成熟的第三方库可能会对系统特定的路径目录造成污染,但是对于成熟的第三方库我们可以直接安装在系统的特定路径下,所以现在由于小编编写的第三方库仅仅是一个为了配合小编讲解静态库的第三方库,并不成熟,所以下面小编将我们自己的第三方库sudo提权rm卸载一下

在这里插入图片描述

这样就可以在命令行中不带搜索头文件以及静态库的搜索路径了,因为gcc会去系统的头文件路径/usr/include下以及系统的库路径/bin64 下去进行搜索,此时我们已经将我们提供的头文件以及静态库安装在了系统的路径下,所以gcc自然可以搜索到,但是由于系统的库路径下存在很多库,同样的gcc并不知道要搜索链接哪一个,所以对于库的名称我们仍然需要使用-l(l是小写的l)选项告诉gcc我们要在库路径下搜索链接哪一个库

在这里插入图片描述

所以此时作为用户,我们不光应该使用-L选项告诉gcc去哪一个路径下去找静态库,同时还应该使用-l选项(这里的l是小写的l)告诉gcc去找哪一个静态库,以libXXX.a为例,那么静态库的真实名称是XXX,并且使用-l选项的时候,一般将要搜索的库名称和-l放在一起,即-lXXX这种形式,所以我们再使用-l选项告诉gcc去找哪一个静态库,看gcc是否可以找到静态库并链接形成可执行

在这里插入图片描述

那么同样的,对于头文件,我也仅仅是告诉了你gcc去哪一个路径下去找,你gcc可以找到对应的头文件,到了静态库这里告诉你gcc去哪一个路径下去找,你却找不到。是的确实找不到,因为对于头文件,其实你是告诉了我gcc要找哪一个头文件,因为你用户包的头文件就是我gcc要找的头文件,但是目前用户却没有告诉我要在你用户指定的路径下去找哪一个静态库

在这里插入图片描述

但是我们自己提供的第三方库并没有在这个库中,所以gcc去/lib64这个系统路径下自然也没有办法进行链接,如何解决呢?我们可以给gcc类似于头文件一样提供搜索路径,即gcc -L 库文件路径,这个-L选项中的L是lib库的意思,这样使用-L选项gcc就回去指定的路径下去搜索我们的库了

在这里插入图片描述

此时我们应该敏感起来,以.o为后缀的一定是链接错误,其实为什么我们日常在进行c/c++编写,使用c/c++库的方法的时候,使用gcc/g++进行编译形成可执行的时候没有出现过这个链接错误,因为系统中早就已经在特定的系统路径下 /lib64下早已经提前安装好了对应的c/c++的库,所以gcc/g++在进行搜索的时候,就会去指定的系统路径下搜索c/c++的库进行链接,以静态库为例,链接的时候,将main函数中用到的库函数方法从静态库中拷贝到main函数中,这样就链接完成,也不会报错

在这里插入图片描述

观察上图,此时我们给gcc使用 I 选项添加上头文件的搜索路径后,此时gcc可以找到头文件#include "mymath.h"了,可是怎么又出现问题了?错误提示在mian函数中找不到Add,Add函数方法未定义,这又改如何理解,此时我们看到下面的错误提示中有一个cc5Aooqk.o

在这里插入图片描述

第二个方法,gcc -I 路径,即gcc添加选项I,I是大写的i,-l的意思是include,即头文件的意思,在这个选项后面跟上头文件的路径即可,这样gcc在搜索头文件的时候,就回去我们指定的路径下去搜索头文件了

在这里插入图片描述

是的,用户,我gcc确实找不到,因为我gcc要求的头文件要在和main.c的同一级目录下,不能够是在和mian.c的同级目录lib的目录下,我gcc只会现在系统的头文件路径 /usr/include下,以及当前目录路径下的同级目录进行查找,我们提供的头文件即不在系统的特定头文件/usr/include路径下,同时也不在当前目录路径的同级目录,所以自然会找不到,那么怎么办呢?

在这里插入图片描述


在这里插入图片描述

如果想要运行main.c首先就要使用gcc编译形成可执行,那么我们使用gcc之后结果缺是下面这样,这究竟是什么原因?明显的,这个错误信息提示我们找不到头文件mymath.h,你逗我呢,gcc?明明这个mymath.h这个头文件就在当前目录的lib目录里面,你却告诉我找不到

在这里插入图片描述
#include"mymath.h"intmain(){printf("5 / 0 = %d, errno = %d\n",Div(5,0), myerrno);return0;}
运行结果如下

奇怪,小编明明使用的是我们自己的除数Div方法,此时进行了除零错误,应该将myerrno设置为1,表示出现除零错误,可是这里为什么errno仍然是0呢?
  1. 观察一下右边的函数调用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了
  2. 如何调整呢?很简单,先让Div(5, 0)执行,接收Div(5, 0)的返回值ret,将ret和errno一并打印即可
#include"mymath.h"intmain(){int ret =Div(5,0);printf("5 / 0 = %d, errno = %d\n", ret, myerrno);return0;}
运行结果如下

所以此时除零错误返回-1,表示结果出错,但是如果没有显示的打印出5 / 0,此时我们就无法区分这个结果-1究竟是正确结果还是出错返回的-1,所以类似的我们就提供了一个全局变量myerrno,当myerrno不为0的时候用于表征出现错误,myerrno具体是哪一个数字表示出错的原因
  1. 同样的c语言库中的errno也是类似于我们设计的myerrno这样的原理,errno也是c语言库提供的一个全局变量,errno为0表示没有出错,当errno不为0的时候用于表征出现错误,errno具体是哪一个数字表示出错的原因,并且我们还应该认识到库中不仅仅能够提供方法,同时也能提供其它的变量,诸如我们这里的errno全局变量,以及其他的诸如FILE流,#define宏定义,typedef的类型定义,结构体struct,联合union,枚举enum,条件编译#pragma once等等
  2. 所以对于静态库我们应该有下面的认知,1. 当我们使用第三方库的时候,往往必定要使用gcc -l,2. 如果系统中只提供静态库,那么gcc只能对该库进行静态链接,3. 如果系统中需要链接多个库,则gcc可以链接多个库

总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!

Read more

Windows/Linux双平台保姆教程:用DDNS-GO v6.7.6实现免费内网穿透(替代花生壳)

从零构建你的专属动态域名服务:告别付费内网穿透,拥抱开源DDNS-GO 最近和几个独立开发者朋友聊天,大家普遍吐槽的一个点就是内网穿透服务。无论是为了远程调试家里的NAS,还是想临时给客户演示一个部署在本地开发机的Web应用,传统的方案要么像花生壳这类工具需要付费且流量受限,要么配置复杂得让人望而却步。更别提一些云服务商提供的穿透服务,按流量计费的模式对于高频测试来说,成本完全不可控。其实,如果你手头有一个公网IP(哪怕是动态变化的),或者你的IPv6环境是通畅的,完全没必要依赖第三方付费服务。今天,我们就来深入聊聊如何利用一个名为 DDNS-GO 的开源神器,亲手搭建一套稳定、免费且完全自控的动态域名解析系统,彻底摆脱对商业内网穿透工具的依赖。 DDNS-GO 的核心价值在于它的“桥梁”作用。它持续监测你本地网络的公网IP地址(包括IPv4和IPv6),一旦发现IP发生变化,就立刻调用云解析服务商(如阿里云、腾讯云DNSPod、Cloudflare等)的API,自动将你指定的域名更新解析到新的IP上。这样一来,无论你的网络环境如何变动,通过一个固定的域名,你总能从外网访问到家里的

By Ne0inhk
Flutter 组件 dart_dev 适配鸿蒙 HarmonyOS 实战:效能基座方案,构建全生命周期自动化开发流水线与研发套件治理架构

Flutter 组件 dart_dev 适配鸿蒙 HarmonyOS 实战:效能基座方案,构建全生命周期自动化开发流水线与研发套件治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 dart_dev 适配鸿蒙 HarmonyOS 实战:效能基座方案,构建全生命周期自动化开发流水线与研发套件治理架构 前言 在鸿蒙(OpenHarmony)生态迈向大规模工业化协同、涉及海量跨端功能并发验证及严苛代码交付质量标准的背景下,如何实现研发流程的“机器化”约束,已成为决定团队产出稳定性与效能上限的关键。在鸿蒙设备这类强调 AOT 极致性能与多包(HAP/HSP)协同部署的环境下,如果研发环节依然依赖分散的散装脚本或非标的 Git 工作流,由于由于环境配置的微差异,极易由于由于“本地通过,远端爆炸”导致集成交付效率的高频损耗。 我们需要一种能够统一任务调度(Task Runner)、支持全量规范校验且具备“一站式”研发脚本治理能力的基座方案。 dart_dev 为 Flutter 开发者引入了“研发即代码(Dev-as-Code)

By Ne0inhk
Linux 底层深入:目标文件、ELF 格式与程序加载全解析

Linux 底层深入:目标文件、ELF 格式与程序加载全解析

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 目标文件:编译后的 “半成品” * 1.1 目标文件的本质 * 1.2 目标文件的生成与验证 * 1.3 目标文件的核心问题:未解析的外部符号 * 二. ELF 文件:Linux 下的 “万能二进制格式” * 2.1 ELF 文件的四大类型 * 2.2 ELF 文件的核心结构 * 2.2.1 ELF 头:文件的 “身份证” * 2.2.

By Ne0inhk

OpenClaw(AI Agent) Ubuntu 系统部署教程(附带接入微信教程,使用阿里云百炼免费API)

众所周知,最近OpenClaw 的火爆证实了大模型Agent的可能性,博主也是本着探索的精神尝试着体验了一下,发现这个东西意外的好用。它的好处这里就不赘述了,这篇博客意在给各位提供一个参考,具体每个人遇到的问题不同,也可以在评论区里讨论 一、准备工作 1、开通百炼API OpenClaw 支持添加自定义模型提供商或与OpenAI/Anthropic 兼容的代理服务。我们选择阿里云的百炼平台,是因为它有免费的初始额度。 我们首先要开通阿里云的百炼账号: 大模型服务平台百炼控制台https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market 开通之后,会赠送我们多个模型的免费token,但是要注意是有时限和额度的,如果不想产生额外费用,可以开启模型的免费额度用完即停功能,这里就不赘述了。 我们需要在密钥管理处申请一个API key,用于调用模型,位于网页的左下角,请记住你的API key,等下会用到   2、前置库的安装 注意,如果你使用阿里云服务器,会有一键安装OpenC

By Ne0inhk