初识——【Linux】make和makefile

ops/2025/1/31 9:44:21/

make和makefile是linux系统里用于自动化编译和构建程序的工具。们通过定义一系列的规则来指定如何编译和链接程序,从而简化了编译过程,尤其是有多个源文件的时候。

下面我们来浅浅的了解一下make和makefile是如果进行自动化编译的。

一.make和makefile

简单来说,make是一个指令,而makefile是一个文件

make会读取名为makefile的文件,然后根据makefile文件里面的指令执行相应的命令。

下面写一个简单的makefile:

当前目录下,我们有一个main.cpp文件,和一个makefile文件,我们现在当然可以直接编译main.cpp文件g++ main.cpp -o main 然后生成一个名为main的可执行程序。

现在我们不用g++命令来编译,而是借助make和makefile来实现自动化编译

先看结论:

执行make命令之后,他会在makefile文件里面从上到下搜索,将第一个文件作为是目标文件即main。而目标文件是依赖于main.cpp这个文件的。有了依赖关系之后,还需要有依赖方法,即第二行的g++命令。

这段指令是什么意思呢? 

文件的第一行 main:main.cpp表示一组依赖关系,即main文件依赖main.cpp文件。

第二行表示了它们的依赖方法,make的时候,就会执行该依赖方法。

第五行是clean目标,但其没有依赖关系,只有依赖方法。但一个makefile文件只能有一个目标文件,而它是从上到下扫描文件的,所以第一个文件就是目标文件。目标文件可以直接make,而其余的文件则需要借助make显式调用(make clean)。

.PHONY:后面的是伪目标,伪目标意味着总是会被执行。即后面的clean会总是被执行。

 二.总是被执行?

什么是总是被执行呢?先观察下面这一现象:

我们make了之后产生了 可执行程序,我们如果再想make的话就不行了,这是为啥呢?

make是一个自动化的编译工具,它只会编译那些已经被更新了的程序。如果make发现之前已经编译了一次,且源代码并没有改变,此时就会拒绝编译。

而我们的make clean就可以被重复执行,这就是总是被执行。

所以我们可以用.PHONY修饰main,使其成为一个伪目标,这样就可以多次make了

那么make是怎么知道依赖的文件有没有被修改呢? 

 文件=内容+属性,而他的属性包含了有三个时间:

  • Modify:表示文件内容被修改的时间
  • Change:表示文件属性被修改的时间 
  • Access:表示文件的访问时间

前两种很好理解,且需要注意,文件内容修改了,则文件属性一般情况下也被修改了,因为size也是一个文件属性。

访问时间指的是你修改了文件或者cat了文件,但是访问时间不准,你刚才编辑了文件,访问时间修改,但是接下来cat文件,它的访问时间是不变的。这中间有冷却时间。

综上,make就是借助Modify time来确定文件的老旧的。如果main的Modify在main.cpp之前,表示main.cpp已经更新了,所以此时就可以重新编译了

 三.makefile的推导过程

我们刚才是直接将.cpp编译成为可执行程序,那我们如果分布编译呢?

既然是分布编译,则我们最终的可执行程序main就是从.o文件来的。即main依赖于main.o,但是当前目录中并没有main.o,此时make就会继续向下寻找,make.o依赖于main.s,而目录中也没有main.s,所以就会继续向下搜索,最终发现,所有的依赖关系到了main.cpp,而该文件是有的,所以make就从这个位置开始向上执行。

其实make遇到依赖关系之后,如果找不到依赖关系,就会先将该依赖关系的依赖方法入栈,然后扫描下一个依赖关系,如果某个依赖关系的依赖文件存在,则从该位置开始执行,接着依次取出栈顶的方法执行。

下面观察运行结果: 

 如果make在扫描的过程中,最后找不到依赖文件,就会停止工作:

四. makefile的扩展语法

makefile里面可以定义变量:

 一般将最终的可执行程序定义为BIN,SRC即为源文件,CC为编译器,FLAGS表示编译选项,RM则表示清理,变量所指代的东西是可以带空格。

接下来我们在写依赖关系和依赖方法时就可以使用变量来写,但是变量得用$()包围。对于依赖方法来说,可以用$@表示目标文件,$^表示依赖对象。它与用变量直接表示是一样的。

但是我们一般习惯于先将源文件编译成目标文件,最后在进行链接,那么makefile怎么写呢?

我们可以定义出链接选项和编译选项,然后根据上面的推导过程进行书写。 对于编译过程,不需要指定obj文件,-c选项会自动生成同名.o文件。

一个依赖关系下面可以有多个依赖方法,且依赖方法可以通过@实现不可视化:

 那么如果有多个源文件呢?如果进行先编译后链接呢?

我们在定义变量时可以使用shell命令获取所有的.cpp文件,也可以将所有的.cpp生成同名.o

 

五.实现进度条 

1.换行和回车的区别

我们在屏幕上打印信息时,可以通过\n来换行,但这里面不仅仅是一个换行解决的。

\r:回车,使光标回到最左端

\n:换行,使光标直接移动到下一行,并不回到下一行的开始位置。

所以我们以前printf("%d\n",a);其实这个\n被处理成了\r\n.

2.缓冲区刷新 

我们先观察下面这段程序:

我们向屏幕打印hello world,然后让程序休眠。我们来观察打印结果:

 

我们看,我们明明先打印的信息,后进行休眠,为什么执行时而是先休眠后打印呢?

我们知道顺序语句,所以程序一定是先打印,后休眠的。但为什么会出现这个结果呢? 

归根结底,是因为printf不会第一时间把内容输出到屏幕上,而是先放入一个缓冲区中,而我们并没有刷新缓冲区的行为,所以并不会进行打印。最后程序结束,会自动刷新缓冲区,使信息打印成功。

所以我们在printf之后,可以先用fflush函数刷新一下缓冲区:

而\n换行符就可以刷新缓冲区。

 3.倒计时小程序

在实现进度条之前,我们先结合上面的小知识,实现一个倒计时小程序

 %-2d可以控制输出的位数,以及让数字左对齐。/r则是控制光标回到起始位置打印。

 4.实现进度条

我们采取声明和实现分离。分别用main.c来控制运行逻辑,progressbar.h包含头文件以及函数声明,progressbar.c包含函数实现。

下面是实现进度条的主要代码:

 我们定义一个字符数组,用来表示当前的进度,每个字符都表示1个进度。即一共有100个字符。所以开101个空间。打印时有三个方括号,一个表示进度条本身,一个表示当前的进度百分比,一个是动态字符,用来显式进度条正在执行。每打印一个进度就休眠。

5.进度条与下载任务结合 

进度条一般不会自己凭空执行,他会在下载软件或上传资源时显式。所以它一般是被下载任务所调用。


完~


http://www.ppmy.cn/ops/154444.html

相关文章

网络安全(黑客)——自学2025

🤟 基于入门网络安全/黑客打造的:👉黑客&网络安全入门&进阶学习资源包 前言 什么是网络安全 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“…

Docker Desktop 在Windows 环境中开发、测试和运行容器化的应用程序

Docker 为 Windows 提供了专门的桌面版工具,称为 Docker Desktop,它允许你在 Windows 环境中开发、测试和运行容器化的应用程序。 如何在 Windows 上使用 Docker Docker Desktop Docker Desktop 是一个专为 Windows 设计的应用程序,它简化了…

服务器上安装Nginx详细步骤

第一步:上传nginx压缩包到指定目录。 第二步:解压nginx压缩包。 第三步:配置编译nginx 配置编译方法: ./configure 配置编译后结果信息: 第四步:编译nginx 在nginx源文件目录中直接运行make命令 第五步&…

基于微信小程序的校园二手交易市场的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…

GESP2023年12月认证C++六级( 第三部分编程题(1)闯关游戏)

参考程序代码&#xff1a; #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <string> #include <map> #include <iostream> #include <cmath> using namespace std;const int N 10…

基于OSAL的嵌入式裸机事件驱动框架——整体架构调度机制

参考B站up主【架构分析】嵌入式祼机事件驱动框架 感谢大佬分享 任务ID &#xff1a; TASK_XXX TASK_XXX 在系统中每个任务的ID是唯一的&#xff0c;范围是 0 to 0xFFFE&#xff0c;0xFFFF保留为SYS_TSK_INIT。 同时任务ID的大小也充当任务调度的优先级&#xff0c;ID越大&#…

Node.js 中文编码问题全解析

Node.js 中文编码问题全解析 问题背景 在 Node.js 中执行 Gradle 命令时遇到中文输出乱码问题。这个问题涉及 Windows 系统、Java 进程和 Node.js 三个层面的编码处理。 问题分析 最初的错误代码 gradleProcess.stdout.setEncoding(utf-8); // 错误&#xff1a;假设输出是…

for...in 和 Object.keys().forEach的区别

for…in 和 Object.keys().forEach的区别 1、遍历范围&#xff1a; for…in 会遍历 自身及原型链上的可枚举属性&#xff0c;需用 hasOwnProperty 过滤。 Object.keys() 仅遍历 自身可枚举属性&#xff0c;更安全。 // 定义一个父对象&#xff0c;包含原型链上的属性 const…