Linux:基础开发工具之Makefile和缓冲区的基本概念

文章目录

  • 动静态库
  • 自动化构建代码
  • 缓冲区
  • 原理实现
  • 具体实现

动静态库

首先要知道什么是链接:

C程序中,并没有定义printf的函数实现,且在预编译中包含的stdio.h中也只有该函数的声明,而没有定义函数的实现

系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到
系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函
printf了,而这也就是链接的作用

因此,链接的理解:

  1. 我们实现的代码,很多都是用了别人帮我们写好的函数
  2. 那这些函数是在哪里?如何看到这些函数?就是库的概念

那么动静态库如何理解?有什么区别?

从库的命名来看:
动态库的命名是使用的.so文件的后缀通常是.dll
静态库的命名是使用的.a文件的后缀通常是.lib

那动态库和静态库如何使用,具体是如何作用的?

所谓动态库,就是cc++或其他第三方提供方法的集合,所有的程序用链接的方式关联起来,在库中的所有的函数都有一个入口地址,而动态链接就是把链接的库的函数地址拷贝到可执行程序的特殊位置

静态库和动态库相同,也是一个方法的集合,但是是将所有应用程序以拷贝的方式将需要的代码直接拷贝到可执行程序当中,这样就是静态链接的原理

因此优缺点也就看出来了,动静态库各有利弊:

  1. 对于动态库来说,使用动态链接生成的可执行程序体积很小,比较节约资源,但是从原理上就能看出,动态链接非常依赖动态库,如果动态库被意外损毁,那么整个和这个动态库相关的程序都不能使用了
  2. 对于静态库来说,优点就是可以无视库的概念,可以直接把库内的东西拷贝到本地进行使用,可以进行独立运行,但是缺点也很明显,那就是体积过大,太过于浪费资源,因为要把代码都考取到本地的地方

总结

因此,可以这样进行总结,我们的代码与头文件再加上库就可以被形成可执行程序

所以,所谓的开发环境的安装,其实就是安装下载并拷贝头文件和库文件到开发环境中的特定路径下,让编译器可以找到

所谓开发环境要做到什么才能被称之为开发环境?

  1. 下载开发环境中includelib
  2. 设置合理的查找路径
  3. 规定好形成可执行程序的链接方式

自动化构建代码

在大型文件中会有几百个源文件,那这些文件的编译工作和编译顺序是一个很繁重的事,那么如果有一个工具可以帮助我们进行这些编译的工作,就会节约很大一部分时间,因此就产生了自动化构建代码的工具:make/Makefile

make是一个命令
makefile是一个在当前目录下存在的一个具有特定格式的文本文件

下面开始具体使用makefile:

首先,既然makefile是一个用来进行项目自动化构建的工具,那么首先必须要有可以运行的文件,因此要首先写一个c文件:

在这里插入图片描述

这是一个程序用来倒序输出一段数字,下面要使用makefile工具来进行自动化构建,这个工具的创建十分简单,直接在目录下创建一个Makefile命名的文件即可,接着在这个文件内可以进行一些输入:

mybin:code.cgcc code.c -o mybin
.PHONY:clean
clean:rm -f mybin

上面是较为基本的版本,一开始的mybin表示最后要生成的文件名字是mybin,而生成mybin需要借助的文件是code.c,对于code.c的操作是用gcc进行编译,这样就可以生成mybin文件,这里要引入两个概念:依赖关系和依赖方法:

何为依赖关系?简单来说,依赖关系就是要完成这个操作需要依赖于什么文件,对于这个文件内的信息来说,依赖关系就是mybin文件的生成需要依赖于code.c这个文件

那何为依赖方法?就是要生成目标文件需要进行什么样的操作,对于这个文件内的信息来说,依赖方法就是要进行gcc code.c -o mybin,这样就能生成mybin,因此这个操作也就被叫做是依赖方法

因此,这样就写好了最初始版本的Makefile文件,这个工具就可以进行一些初步的操作:

在这里插入图片描述
从上面的图中可以看出进行操作的一些步骤,首先当前目录下只有一个code.c和项目自动化构建工具,使用make指令后,就在当前目录下使用gcc编译器完成了编译,生成了mybin文件,此时当前目录下就生成了mybin这个可执行文件,而这个可执行文件运行后的结果也可以被打印出来

下面的操作是make clean,通过这个指令可以调用Makefile文件下的clean指令,可以执行rm命令,将生成的mybin可执行文件进行删除

那现在再次看到Makefile文件,文件内的信息中除了总结的依赖关系和依赖方法外,还有一些没见过的内容,这些内容的意义是什么?

首先是.PHONY:clean,这个语句中有一个.PHONY,这个的意义是作为一个伪目标,那什么又是伪目标?下面进行解释

先说结论,伪目标的意义就是可以让依赖方法总是被执行,不会收到任何情况的阻拦,也就是说如果被确认的伪目标,那么语句总是会被执行的

那问题又来了,什么叫总是被执行,难道还有不会被执行的情况吗?有下面的实验

在这里插入图片描述
上面的实验就很好的说明了这个问题,在这个实验中,第一次使用make指令是可以成功的,可以让code.c经过gcc编译,但是如果继续使用make,就会提示上面的报错信息,而这个报错信息产生的原因其实是因为mybin文件不需要被更新,因此这个自动化构建的工具没有让代码进行编译,也就是说,通过一些比较,是可以让一些代码不再重新编译的,或者总结来说,可以让一些操作不被重复执行,因为执行这些操作没有必要

为什么要这样设置?有什么好处?

为什么Makefilemake不让使用者随时重新编译代码,而是会做出一定的限制?原因在于要提高效率,那么又是如何进行判断的?答案是通过时间进行判断,如果通过时间的判断,Makefile工具发现,mybin实际上不需要重新编译,因为code.c内没有进行任何操作,和之前的编译结果产生是一样的,此时就会不让用户进行编译,这样也能提高效率

那工具是如何做到的?如何进行比较文件的时间,文件的时间又是一个什么样的概念?
因此就引出了文件的ACM时间的概念

想要看到时间需要用到Linux中的一条指令:

stat code.c

在这里插入图片描述
借助这个指令可以看到关于code.c文件的各种信息,其中就包括文件的几个修改时间,下面先看文件的这几个修改时间有什么具体的意义:

首先要清楚前面的一个概念:文件=内容+属性,因此在这里关于文件的修改时间,是包括文件的内容修改时间和文件的属性修改时间

Access时间:Access修改时间指的是文件的内容被查看就会被修改,但Access并非每次被查看都会被记录到这次的时间变化,原因看后续

Modify时间:Modify时间指的是文件的内容被改变的时候,就会改变一次这个时间

Change时间:Change时间指的是文件的属性被修改的时候,就会改变一次这个时间

现在使用vim进入文件中对文件进行一些修改,此时再看文件的各项时间:

在这里插入图片描述

从中可以看出,使用vim对文件进行了一些操作后,时间都被修改了,AccessModify时间被修改是因为文件内容被修改,而文件内容被修改意味着文件的属性也必然会被修改,因此文件的Change时间也被进行了修改,三个时间都进行了一定程度的修改

因此可以从中总结出,Change时间被修改是当文件的属性发生变换时就会修改,因此Change时间是可以单独被修改的,而Modify时间的改变必然会导致其他时间跟着连锁改变,因为文件内容的改变会导致其属性必然改变

回到前面的问题,那这个工具是如何判断什么时候要进行修改?

答案是根据的是Modify的时间来判断,通过Modify的时间就可以对比出来源文件和编译生成的可执行程序的新旧,如果编译出来的可执行程序的时间晚于源文件,就意味着源文件可能被进行了某些修改,需要重新编译,因此此时就具备重新编译的条件,就可以重新编译,但是如果源文件的修改时间晚于可执行程序,就可以被认为源文件在编译形成可执行程序的这段时间内没有进行其他操作,没有重新编译的需要,为了保证效率就不再进行编译,而是直接采用编译好的可执行文件运行即可

Access时间的特殊性

Linux中可以看到的文件,都是在磁盘上进行的存储,而又由于文件是由内容加属性组成的,因此更改时间的本质其实就是访问磁盘

Access时间负责管理的是文件被查看的频率,而如果文件每次被查看都要更改对应的Access时间,那么访问磁盘的频率就过快,而在Linux系统中会存在大量的访问磁盘的IO操作,如果每次查看后都要访问磁盘进行修改时间,会降低系统的效率,因此对于Access时间做了一定的限制,增加了次数限制,因此并不是每次查看文件都会更改这份文件的Access时间

缓冲区

C语言中学习过缓冲区,那时的缓冲区是站在输入输出的角度来看的,现在在Linux中重新认识缓冲区的概念

在c语言中,针对输出流有其特定的默认缓冲区,这里主要研究输出缓冲区,输出缓冲区的位置在哪里?如何证明输出缓冲区的概念?

现在有这样的程序:

#include <stdio.h>
#include <unistd.h>int main()
{printf("hello linux");sleep(2);return 0;
}

对这个程序编译运行,会发现一个现象:程序会先进行sleep后进行输出打印,按照正常逻辑来看,程序理应是先输出信息再sleep,那么从这个现象中就可以看出,在运行程序的过程中,输出缓冲区确实是存在的,而要输出的信息就存储在输出缓冲区中,还没有被打印到屏幕上,因此会先sleep后再进行显示到屏幕

那么如何处理缓冲区?可以用到缓冲区对应的函数

fflush函数

#include <stdio.h>
#include <unistd.h>int main()
{printf("hello linux");fflush(stdout);sleep(2);return 0;
}

或者也可以直接输出后打上\n,原因是回车换行也是一种刷新的策略

有了上面的基础知识,回顾c语言:

\r = 回车键
\n= 换行键

而键盘中的enter键,实际上就是回车+换行

因此基于上面的原理,可以实现一个有趣的代码

#include <stdio.h>
#include <unistd.h>int main()
{int a=5;while(a--){printf("%d\r",a);sleep(1);}return 0;
}

上面代码输出的结果是没有结果,原因在于:每当打印一个数字的时候,这个数字一直在缓冲区中,而\r直接把数据进行了覆盖

如果将代码更新为这样:

#include <stdio.h>
#include <unistd.h>int main()
{int a=5;while(a--){printf("%d\r",a);fflush(stdout);sleep(1);}return 0;
}

对于缓冲区的理解

首先要清楚,缓冲区的本质是一个数组,因此对于单句的printf函数,如果用这个语句来看的话,缓冲区中会存储的是a的值和\r,假定这里没有fflush函数也没有sleep函数,那么printf函数就会把所有数据缓存到缓冲区,当程序结束后以此进行打印,把数据和\r看成一组,每执行完数据和\r后光标就回到了最开始的地方

本篇在Linux下基于前面所学缓冲区和Makefile的概念再加一些函数实现一个进度条

原理实现

  1. 借助Makefile的原理项目自动化构建
  2. 借助缓冲区的概念

具体实现

首先实现基础的功能版本

实现原理其实很简单,就是利用缓冲区的概念,打印出信息后借助\r回到最开头的地方,再借助缓冲区把信息打印出来即可

这里做了一些最基础的优化,例如在后面加了百分比和模拟实现了一个滚动实现的效果

// process.h
#include <stdio.h>
#include <unistd.h>
#include <string.h>#define MAXSIZE 101
#define process_char '#'
#define sleep_time 10000void process_v1();
// process.c
#include "process.h"char str[]="|\\-/";void process_v1()
{int rate=0;char arr[MAXSIZE]={0}; memset(arr,'\0',sizeof(arr));while(rate<MAXSIZE){printf("[%-100s][%d%%][%c]\r",arr,rate,str[rate%strlen(str)]);fflush(stdout);usleep(sleep_time);arr[rate++]=process_char;}printf("\n");
}
// main.c
#include "process.h"int main()
{process_v1();return 0;
}

写完源文件就应该借助项目自动化构建工具完成简单的编译清除功能了

process:process.c main.cgcc -o $@ $^
.PHONY:clean
clean:rm -f process

在这里插入图片描述
这样最基础的版本就实现完成了,用了宏定义可以随意修改进度条的速度等

但这样的实现和实际还不太一样,下面实现一个和实际开发相适应的进度条版本

下面进行一些优化版本

在实际开发中,进度条并不是单独出现,它会和具体的下载内容相结合,因此是需要进行结合使用的

因此优化版本的思路就是在将进度条和一个具体的下载情景结合起来使用,借助一个回调函数实现,利用休眠函数模拟下载的这个过程

// process.h
#include <stdio.h>
#include <unistd.h>
#include <string.h>#define MAXSIZE 101
#define process_char '#'
#define sleep_time 10000
#define process_head '>'
#define process_body '='
#define TARGET_SIZE 1000*1000
#define DSIZE 1000*10
#define MAX_RATE 100void process_v1();void process_v2(int);typedef void (*callback_t)(int);
// process.c
#include "process.h"char str[]="|\\-/";void process_v1()
{int rate=0;char arr[MAXSIZE]={0}; memset(arr,'\0',sizeof(arr));while(rate<MAXSIZE){printf("[%-100s][%d%%][%c]\r",arr,rate,str[rate%strlen(str)]);fflush(stdout);usleep(sleep_time);arr[rate++]=process_char;}printf("\n");
}void process_v2(int rate)
{static char arr[MAXSIZE]={0}; //memset(arr,'\0',sizeof(arr));if(rate<MAXSIZE && rate>=0){printf("[%-100s][%d%%][%c]\r",arr,rate,str[rate%strlen(str)]);fflush(stdout);usleep(sleep_time);if(rate < MAXSIZE-1){arr[rate-1]=process_body;arr[rate]=process_head;}else {arr[rate]=process_body;}}//printf("\n");
}
// main.c
#include "process.h"void download(callback_t cb)
{int testcnt = 100;int target = TARGET_SIZE;int total = 0;while(total <= target){usleep(sleep_time); total += DSIZE;double rate = total*100.0/target;if(rate > 50.0 && testcnt) {total = target/2;testcnt--;}cb(rate);}cb(MAX_RATE);printf("\n");
}int main()
{download(process_v2);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/109521.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机网络】传输层协议——TCP(下)

文章目录 1. 三次握手三次握手的本质是建立链接&#xff0c;什么是链接&#xff1f;整体过程三次握手过程中报文丢失问题为什么2次握手不可以&#xff1f;为什么要三次握手&#xff1f; 2. 四次挥手整体过程为什么要等待2MSL 3. 流量控制4. 滑动窗口共识滑动窗口的一般情况理解…

EasyX图形化界面

这里写目录标题 EasyX绘制简单的图形化窗口窗口坐标设置窗口属性实现基本绘图功能贴图原样贴图透明贴图认识素材 代码步骤 按键交互阻塞按键 鼠标交互 EasyX 绘制简单的图形化窗口 代码示例&#xff1a; while&#xff08;1&#xff09;&#xff1b; 可以防止闪屏 窗口坐标 …

matlab根轨迹绘制

绘制根轨迹目的就是改变系统的闭环极点&#xff0c;使得系统由不稳定变为稳定或者使得稳定的系统变得更加稳定。 在使用PID控制器的时候&#xff0c;首先要确定的参数是Kp&#xff0c;画成框图的形式如下&#xff1a; 也就是想要知道Kp对系统性能有哪些影响&#xff0c;此时就…

设计模式之代理模式的懂静态代理和动态代理

目录 1 概述1.1 如何实现&#xff1f;1.2 优点1.3 缺点1.4 适用场景 2 静态代理实现3 JDK 动态代理实现4 CGlib 动态代理实现5 总结 1 概述 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它的概念很简单&#xff0c;它通过创建一个代理对象来…

便捷又炸街!Mate 60的智感支付,是如何做到快速又安全的?

扫码支付已成为线下消费的主流付款方式&#xff0c;平时出门&#xff0c;手机一带&#xff0c;钱包拜拜&#xff01; 以微信支付为例&#xff0c;正常线下支付&#xff0c;手机解锁状态下&#xff1a; 第一步&#xff1a;找到微信APP&#xff1b; 第二步&#xff1a;打开右上…

合宙Air724UG LuatOS-Air LVGL API控件-窗口 (Window)

窗口 (Window) 分 享导出pdf 示例代码 win lvgl.win_create(lvgl.scr_act(), nil) lvgl.win_set_title(win, "Window title") -- close_btn lvgl.win_add_btn_right(win, "\xef\x80\x8d") -- --lvgl.obj_set_event_cb(cl…

Nacos注册中心

Nacos 安装 https://nacos.io/zh-cn/ 源码安装 第一步&#xff1a;利用Gitee获取nacos在github上的代码到自己的gitee仓库中 https://github.com/alibaba/nacos.git 第二步&#xff1a;下载源码到本地。 第三步&#xff1a;使用maven编译代码。 # 先切换到master分支 gi…

3 分钟,带你了解低代码开发

一、低代码平台存在的意义 传统软件开发交付链中&#xff0c;需求经过3次传递&#xff0c;用户→业务→架构师→开发&#xff0c;每一层传递都可能使需求失真&#xff0c;导致最终交付的功能返工。 业务的变化促使软件开发过程不断更新、迭代和演进&#xff0c;而低代码开发即是…

vi/vim编辑器

vi和vim区别 vi 和 vim 是常见的文本编辑器&#xff0c;以下是它们之间的区别&#xff1a; 功能和特性&#xff1a; vi 是最早的版本&#xff0c;是在早期Unix系统中广泛使用的编辑器。vi 相对较简单&#xff0c;功能主要集中在基本的文本编辑操作上&#xff0c;如插入、删除、…

Python二级 每周练习题18

练习一: 从键盘输入任意字符串&#xff0c;按照下面要求分离字符串中的字符: 1、分别取出该字符串的第偶数位的元素(提醒注意:是按照从左往右数的方式确定字符串的位置) 2、并依次存储到一个列表中; 3、输出这个列表。 答案: ninput(请输入任意字符串:) #创建变量n存放用户…

TGA格式文件转材质

今天淘宝上买了一个美女的模型&#xff0c;是blender的源文件&#xff0c;上面说有fbx格式的。我用unity&#xff0c;所以觉得应该可以用。文件内容如下图&#xff1a; FBX文件夹打开后&#xff0c;内容如下图所示&#xff0c;当时就预感到可能没有色彩。 unity打开后果然发现只…

vue3+scss开启写轮眼

vue3scss开启写轮眼 一、相关技术二、使用步骤1.安装依赖2.眼球3 勾玉4 旋转动画5 综合 一、相关技术 采用vue3vitescss的技术内容进行开发 二、使用步骤 1.安装依赖 代码如下&#xff1a; npm install sass2.眼球 首先我们根据需要 将眼睛的基础形状描绘出来&#xff0c…