当前位置: 首页 > news >正文

Linux Makefile与进度条

一.Makefile

1.初识Makefile与make

Makefile简单来说,它是一种自动化,批量化编译的工具。对于一个真实的项目,需要编译的文件不计其数,哪些需要先编译,哪些需要后编译,具体要做哪些操作,如果这些工作都由我们手工完成会十分浪费时间精力。而Makefile能够把这些工作批量化自动化地完成,极大地简化了项目的管理,甚至可以说,Makefile的编写能力能反映出一个程序员是否具备大型工程文件的编写能力。

2.Makefile的基本使用规则

Makefile和make往往配套使用。Makefile是一个文件,用于编写各种依赖关系依赖操作;而make是一个命令,用于检索Makefile文件并执行其中的相关操作。接下来我们举一个简单的例子:

我们使用Makefile编译code.c文件。

首先创建code.c文件并写入内容。

wujiahao@VM-12-14-ubuntu:~/codetest$ touch code.c
wujiahao@VM-12-14-ubuntu:~/codetest$ vim code.c
wujiahao@VM-12-14-ubuntu:~/codetest$ cat code.c
#include<stdio.h>int main()
{printf("hello makefile");return 0;}

接着我们在同目录下创建一个名叫Makefile的文件,并进行编写。

wujiahao@VM-12-14-ubuntu:~/codetest$ vim Makefile
wujiahao@VM-12-14-ubuntu:~/codetest$ cat Makefile
code:code.cgcc code.c -o code
.PHONY:clean
clean:rm -f code

然后我们执行make命令,可以看到生成了可执行程序code。

wujiahao@VM-12-14-ubuntu:~/codetest$ vim code.c
wujiahao@VM-12-14-ubuntu:~/codetest$ make
gcc code.c -o code

wujiahao@VM-12-14-ubuntu:~/codetest$ ./code
hello makefile

接下来我们来讲述Makefile的编写规则。

3.Makefile语法

首先,我们先明确我们当前的目的:将当前目录的code.c文件编译成可执行文件code。

第一行:这样的分号将两个名称分开,我们称为依赖关系。code为依赖者,code.c为被依赖者。

第二行:这行我们用于填写编译的方式,我们称为依赖方法

第三行:.PHONY是一个关键字,被该关键字修饰的目标文件总是会被执行。要理解这里的总是会被执行,我们首先要理解:不会被重复执行。这是什么意思呢?

例如这里的code.c并没有被关键字修饰,于是:我们再次执行这个指令,发现它显示如下提示。

wujiahao@VM-12-14-ubuntu:~/codetest$ make
make: 'code' is up to date.

那这里的逻辑是什么?

如果你的源文件没有发生改动,编译器是不会对这个未改动的文件重新编译的,这样的设计其实很有逻辑:在一个大型工程中,有成百上千的文件,你只修改了其中的十多个,就没有必要对其他未修改的文件重新编译了,这有利于提升编译效率。

而这里的clean用于项目清理,一些旧的文件我们需要及时清理,否则可能会影响后续的文件编译,而clean被修饰,它就可以被“总是执行”了。

wujiahao@VM-12-14-ubuntu:~/codetest$ make clean
rm -f code
wujiahao@VM-12-14-ubuntu:~/codetest$ make clean
rm -f code

这里思考一个问题:

问题1:make是如何知道源文件发生过改动的呢?

回答1:通过文件的ACM时间,对比源文件和可执行文件的Modify时间戳得出。

ACM时间就是Access(访问时间):只要查看过文件,这个时间戳就会被修改;Change(修改):如果修改过文件的属性,这个时间戳就会被修改;Modify(修改):如果修改过文件内容,这个时间戳进行修改。

对于源文件和可执行文件,他们虽然创建和执行速度非常快,但他们的ACM属性是有所不同的。

wujiahao@VM-12-14-ubuntu:~/codetest$ stat codeFile: codeSize: 15952     	Blocks: 32         IO Block: 4096   regular file
Device: fc02h/64514d	Inode: 540421      Links: 1
Access: (0775/-rwxrwxr-x)  Uid: ( 1002/wujiahao)   Gid: ( 1003/wujiahao)
Access: 2025-09-16 19:24:35.376432511 +0800
Modify: 2025-09-16 19:24:34.592430486 +0800
Change: 2025-09-16 19:24:34.592430486 +0800Birth: 2025-09-16 19:24:34.584430466 +0800
wujiahao@VM-12-14-ubuntu:~/codetest$ stat code.cFile: code.cSize: 78        	Blocks: 8          IO Block: 4096   regular file
Device: fc02h/64514d	Inode: 540547      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1002/wujiahao)   Gid: ( 1003/wujiahao)
Access: 2025-09-16 19:08:14.381895887 +0800
Modify: 2025-09-16 19:08:13.073892402 +0800
Change: 2025-09-16 19:08:13.073892402 +0800Birth: 2025-09-16 19:08:13.073892402 +0800

也就是说,他们当前在时间轴上看起来是这样的:

但是我们一旦对源文件code.c修改:Modify时间戳很明显发生了变化

wujiahao@VM-12-14-ubuntu:~/codetest$ make 
gcc code.c -o code
wujiahao@VM-12-14-ubuntu:~/codetest$ vim code.c
wujiahao@VM-12-14-ubuntu:~/codetest$ stat codeFile: codeSize: 15952     	Blocks: 32         IO Block: 4096   regular file
Device: fc02h/64514d	Inode: 530342      Links: 1
Access: (0775/-rwxrwxr-x)  Uid: ( 1002/wujiahao)   Gid: ( 1003/wujiahao)
Access: 2025-09-16 19:31:50.369558066 +0800
Modify: 2025-09-16 19:31:48.521553273 +0800
Change: 2025-09-16 19:31:48.521553273 +0800Birth: 2025-09-16 19:31:48.509553242 +0800
wujiahao@VM-12-14-ubuntu:~/codetest$ stat code.cFile: code.cSize: 148       	Blocks: 8          IO Block: 4096   regular file
Device: fc02h/64514d	Inode: 540547      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1002/wujiahao)   Gid: ( 1003/wujiahao)
Access: 2025-09-16 19:32:45.369700809 +0800
Modify: 2025-09-16 19:32:43.373695626 +0800
Change: 2025-09-16 19:32:43.377695636 +0800Birth: 2025-09-16 19:32:43.373695626 +0800

而此时源文件和code的关系就成了:

此时再去执行make,就会生成一个新的可执行程序code。

wujiahao@VM-12-14-ubuntu:~/codetest$ make
gcc code.c -o code
wujiahao@VM-12-14-ubuntu:~/codetest$ ./code
hello makefile
this is a change
this is a change

由此我们同时还能理解.PHONY的原理:经过这个关键字修饰的可执行文件,会忽略时间戳直接执行。

tip:touch指令的一个重要作用就是修改已存在文件的时间戳。

4.Makefile的推导

上面的Makefile可以写成如下的调用链:可执行code实际上依赖.o文件,而.o文件又依赖.s文件。依次类推。

由此我们可以看出,make实际上维护着一个类似于栈的结构。我们知道make是从上到下扫描Makefile文件:

我们查看目录,也可以看到指令执行的顺序正好是出栈的顺序。

执行make和make clean,可以正常工作。

5.Makefile语法细化

实际上,Makefile允许我们定义变量,循环等等操作,接下来示范一下细化的Makefile语法。

这里做的工作其实非常简单,相当于创建了一系列变量,然后把依赖关系依赖方法全部用变量替换。

需要注意的只有$(可以理解为解引用拿到实际的对象),以及@(不回显echo字样,仅显示指令执行结果)。

wujiahao@VM-12-14-ubuntu:~/codetest$ make
gcc -o code code.c
wujiahao@VM-12-14-ubuntu:~/codetest$ ./code
hello makefile
this is a change
this is a change
wujiahao@VM-12-14-ubuntu:~/codetest$ make test
code
gcc
code.c

这里要注意:makefile真正的优势区间就是批量化,自动化。我们编写以下的Makefile:

可以通过shell(命令行执行的结果)或者wildcard函数拿到当前目录下所有.c文件,然后链接过程如下。.o文件均由.c文件通过FLAGS编译选项而来,而这可以应对这样的情况:当前文件夹有大量的.c源文件,每一份.c都会对应生成一份.o,然后统一链接为可执行程序BIN。

我们先拿单文件做例子,可以正常运行。

wujiahao@VM-12-14-ubuntu:~/codetest$ make
compling... code.c to code.o
linking... code.o to code 
wujiahao@VM-12-14-ubuntu:~/codetest$ ./code
hello makefile
this is a change
this is a change

然后用超大量文件做例子。

count=1; while [ $count -le 100 ]; do touch code${count}.c; let count++; done

wujiahao@VM-12-14-ubuntu:~/codetest$ count=1; while [ $count -le 100 ]; do touch code${count}.c; let count++; done
wujiahao@VM-12-14-ubuntu:~/codetest$ ls
code100.c  code1.c   code2.c   code3.c   code4.c   code5.c   code6.c   code7.c   code8.c   code9.c
code10.c   code20.c  code30.c  code40.c  code50.c  code60.c  code70.c  code80.c  code90.c  code.c
code11.c   code21.c  code31.c  code41.c  code51.c  code61.c  code71.c  code81.c  code91.c  Makefile
code12.c   code22.c  code32.c  code42.c  code52.c  code62.c  code72.c  code82.c  code92.c
code13.c   code23.c  code33.c  code43.c  code53.c  code63.c  code73.c  code83.c  code93.c
code14.c   code24.c  code34.c  code44.c  code54.c  code64.c  code74.c  code84.c  code94.c
code15.c   code25.c  code35.c  code45.c  code55.c  code65.c  code75.c  code85.c  code95.c
code16.c   code26.c  code36.c  code46.c  code56.c  code66.c  code76.c  code86.c  code96.c
code17.c   code27.c  code37.c  code47.c  code57.c  code67.c  code77.c  code87.c  code97.c
code18.c   code28.c  code38.c  code48.c  code58.c  code68.c  code78.c  code88.c  code98.c
code19.c   code29.c  code39.c  code49.c  code59.c  code69.c  code79.c  code89.c  code99.c

直接make,全部编译链接生成可执行程序code。

wujiahao@VM-12-14-ubuntu:~/codetest$ make
compling... code100.c to code100.o
compling... code10.c to code10.o
compling... code11.c to code11.o
compling... code12.c to code12.o
compling... code13.c to code13.o
compling... code14.c to code14.o
compling... code15.c to code15.o
....
linking... code100.o code10.o code11.o code12.o code13.o code14.o code15.o code16.o code17.o code18.o code19.o code1.o code20.o code21.o code22.o code23.o code24.o code25.o code26.o code27.o code28.o code29.o code2.o code30.o
...
wujiahao@VM-12-14-ubuntu:~/codetest$ ls
code       code19.c  code28.o  code38.c  code47.o  code57.c  code66.o  code76.c  code85.o  code95.c
code100.c  code19.o  code29.c  code38.o  code48.c  code57.o  code67.c  code76.o  code86.c  code95.o
code100.o  code1.c   code29.o  code39.c  code48.o  code58.c  code67.o  code77.c  code86.o  code96.c
code10.c   code1.o   code2.c   code39.o  code49.c  code58.o  code68.c  code77.o  code87.c  code96.o
code10.o   code20.c  code2.o   code3.c   code49.o  code59.c  code68.o  code78.c  code87.o  code97.c
code11.c   code20.o  code30.c  code3.o   code4.c   code59.o  code69.c  code78.o  code88.c  code97.o
...
wujiahao@VM-12-14-ubuntu:~/codetest$ make clean
rm -f code100.o code10.o code11.o code12.o code13.o code14.o code15.o code16.o code17.o code18.o code19.o code1.o code20.o code21.o code22.o code23.o code24.o code25.o code26.o code27.o code28.o code29.o code2.o code30.o code31.o code32.o code33.o code34.o code35.o code36.o code37.o code38.o code39.o code3.o code40.o code41.o code42.o code43.o code44.o code45.o code46.o code47.o code48.o code49.o code4.o code50.o code51.o code52.o code53.o code54.o code55.o code56.o code57.o code58.o code59.o code5.o code60.o code61.o code62.o code63.o code64.o code65.o code66.o code67.o code68.o code69.o code6.o code70.o code71.o 
...

可以看到,要处理的文件量越大,Makefile的用途就越明显。

二.进度条

学习Makefile之后,我们可以制作一个小程序:进度条,就类似于我们下载文件时的那种。

为此,我们需要一些其他前置知识的铺垫。

1.换行与回车

我们需要明确一个概念:回车与换行并不是一个东西。用一张图看懂:

这个设计源于老式打字机:当一行打满时,纸张上移,相当于换行操作;要在下一行写时,从左往有写,需要将指针换到当前空白行的行首,相当于回车操作。

而在计算机中,\r\n为回车换行,而\n往往综合了回车和换行的工作。

2.缓冲区

我们可以把缓冲区想象成一个中间站排队区。它的主要目的是协调两个处理速度不同的设备或程序,让它们能更高效、更平滑地一起工作,避免因为速度不匹配而导致的问题。接下来用一个例子详细解释。

我们编写如下程序

我们会发现,程序在打印出hello buffer之后休眠三秒,自动结束。

wujiahao@VM-12-14-ubuntu:~/codetest$ ./main
hello buffer

而我们将换行符\n去掉之后,则运行结果:似乎是先休眠了三秒,才输出的hello buffer。

问题2:这是怎么回事?难道先执行了sleep吗?

wujiahao@VM-12-14-ubuntu:~/codetest$ ./main
hello buffer

回答2:当然不是!在我们的计算机中,程序的执行一定为顺序执行的。也就是说,当我们看到休眠的过程时,printf函数已经执行过了。

问题3:那为什么屏幕上此刻没有出现hello buffer?

回答3:在休眠3s期间,字符串就在缓冲区中。带有\n的会进行行刷新,不带\n的只会在程序结束时自动刷新缓冲区。

那有没有办法,让我们不用\n也能实现立马刷新缓冲区的效果呢?

当然有,这就是fflush函数的作用。下面我们做一个演示。

可以看到这里实现了一个倒计时的效果。

wujiahao@VM-12-14-ubuntu:~/codetest$ ./main0

3.进度条

首先创建以下文件

wujiahao@VM-12-14-ubuntu:~/codetest$ touch process.h process.c main.c
wujiahao@VM-12-14-ubuntu:~/codetest$ ls
code.c  main.c  Makefile  process.c  process.h

接着编写mian.c,process.c,process.h文件以及Makefile

wujiahao@VM-12-14-ubuntu:~/codetest$ vim Makefile
wujiahao@VM-12-14-ubuntu:~/codetest$ vim process.h
wujiahao@VM-12-14-ubuntu:~/codetest$ vim process.c
wujiahao@VM-12-14-ubuntu:~/codetest$ vim main.c
wujiahao@VM-12-14-ubuntu:~/codetest$ cat *
#include<stdio.h>int main()
{printf("hello makefile\n");printf("this is a change\n");printf("this is a change\n");return 0;
}
#include<process.h>
#include<stdio.h>
#include<unistd.h>double total=1024.0;
double speed=1.0;void DownLoad()
{double current=0;while(current<=total){FlushProcess(total,current);usleep(3000);current+=speed;}printf("\ndownload %.21fMB Done\n",current);
}int main(){DownLoad();return 0;
}
BIN=processbar
SRC=$(shell ls *.c)
OBJ=$(SRC:.c=.o)$(BIN):$(OBJ)gcc -o $@ $^
%.o:%.cgcc -c $<.PHONY:clean
clean:rm -f $(OBJ) $(BIN)#include"process.h"
#include<string.h>
#include<unistd.h>#define NUM 101
#define STYLE '='void FlushProcess(double total,double current)
{char buffer[NUM];memset(buffer,0,sizeof(buffer));const char* lable="|/-\\";int len=strlen(lable);static int cnt=0;int i=0;int num=(int)(current*100/total);for(i=0;i<num;i++){buffer[i]=STYLE;}double rate=current/totalcnt%=len;printf("[%-100s][%.1f%%][%c]\r",buffer,rate*100,lable[cnt]);cnt++fflush(stdout);
}
#pragma once
#include<stdio.h>
void process_v1();
void FlushProcess(double total,double current);

make之后:

wujiahao@VM-12-14-ubuntu:~/codetest$ ll
total 56
drwxrwxr-x 2 wujiahao wujiahao  4096 Sep 16 22:22 ./
drwxr-x--- 8 wujiahao wujiahao  4096 Sep 16 22:27 ../
-rw-rw-r-- 1 wujiahao wujiahao  1640 Sep 16 22:19 code.o
-rw-rw-r-- 1 wujiahao wujiahao   355 Sep 16 22:19 main.c
-rw-rw-r-- 1 wujiahao wujiahao  2008 Sep 16 22:19 main.o
-rw-rw-r-- 1 wujiahao wujiahao   143 Sep 16 22:05 Makefile
-rwxrwxr-x 1 wujiahao wujiahao 16432 Sep 16 22:22 processbar*
-rw-rw-r-- 1 wujiahao wujiahao   542 Sep 16 22:21 process.c
-rw-rw-r-- 1 wujiahao wujiahao    98 Sep 16 22:06 process.h
-rw-rw-r-- 1 wujiahao wujiahao  2400 Sep 16 22:21 process.o
wujiahao@VM-12-14-ubuntu:~/codetest$ make
make: 'processbar' is up to date.

clean之后

wujiahao@VM-12-14-ubuntu:~/codetest$ make clean
rm -f main.o process.o processbar
wujiahao@VM-12-14-ubuntu:~/codetest$ ll
total 28
drwxrwxr-x 2 wujiahao wujiahao 4096 Sep 16 22:28 ./
drwxr-x--- 8 wujiahao wujiahao 4096 Sep 16 22:27 ../
-rw-rw-r-- 1 wujiahao wujiahao 1640 Sep 16 22:19 code.o
-rw-rw-r-- 1 wujiahao wujiahao  355 Sep 16 22:19 main.c
-rw-rw-r-- 1 wujiahao wujiahao  143 Sep 16 22:05 Makefile
-rw-rw-r-- 1 wujiahao wujiahao  542 Sep 16 22:21 process.c
-rw-rw-r-- 1 wujiahao wujiahao   98 Sep 16 22:06 process.h


文章转载自:

http://40e2I7HR.fppzc.cn
http://BXmvKI1e.fppzc.cn
http://E8NbRtjN.fppzc.cn
http://sH9qO45u.fppzc.cn
http://IJC4nyYE.fppzc.cn
http://iQM6gw5A.fppzc.cn
http://6xuku66c.fppzc.cn
http://NuhwOc8Z.fppzc.cn
http://zBOotNjj.fppzc.cn
http://3O0SHDWP.fppzc.cn
http://Ni0SiL6T.fppzc.cn
http://sQTw2mwO.fppzc.cn
http://OPJHvJrk.fppzc.cn
http://Vcub3pk3.fppzc.cn
http://szqx9WWL.fppzc.cn
http://4bCCPFDJ.fppzc.cn
http://pW3Vrdu3.fppzc.cn
http://KuKtA9N9.fppzc.cn
http://yJeUA9f2.fppzc.cn
http://irdRs9KL.fppzc.cn
http://YaCY9MTJ.fppzc.cn
http://vAeljfw6.fppzc.cn
http://CV7UD2go.fppzc.cn
http://vr2m80Hm.fppzc.cn
http://MTyr4TJ7.fppzc.cn
http://nHwcd681.fppzc.cn
http://89ovD5X1.fppzc.cn
http://5Ou80Dmd.fppzc.cn
http://wvY0r5jQ.fppzc.cn
http://NEk5BOAE.fppzc.cn
http://www.dtcms.com/a/387114.html

相关文章:

  • 硬件驱动——I.MX6ULL裸机启动(3)(按键设置及中断设置
  • 深度学习基本模块:RNN 循环神经网络
  • 【深度学习】PixelShuffle处理操作
  • 10.1 - 遗传算法(旅行商问题C#求解)
  • Java 集合入门:从基础到实战的完整知识指南
  • 《过山车大亨3 完整版》PSXbox版下月推出 预告片赏
  • P1107题解
  • 多目标数据关联算法MATLAB实现
  • 战略推理AI Agents:组装LLM+因果推断+SHAP
  • 【CVPR 2016】基于高效亚像素卷积神经网络的实时单幅图像与视频超分辨率
  • 基于STM32的LED实战 -- 流水灯、呼吸灯、流水呼吸灯
  • 【数据结构】——队列,栈(基于链表或数组实现)
  • 任天堂官网更新!“任亏券”不支持兑换NS2专用游戏
  • 大模型数据整理器打包及填充、Flash Attention 2解析(97)
  • 48v转12v芯片48v转5v电源芯片AH7691D
  • Oracle Database 23ai 内置 SQL 防火墙启用
  • MySQL 31 误删数据怎么办?
  • 微前端面试题及详细答案 88道(09-18)-- 核心原理与实现方式
  • VBA技术资料MF362:将窗体控件添加到字典
  • 【Leetcode】高频SQL基础题--1321.餐馆营业额变化增长
  • Redis 中 Intset 的内存结构与存储机制详解
  • uniapp打包前端项目
  • cka解题思路1.32-3
  • 如何解决模型的过拟合问题?
  • 2025牛客周赛108场e题
  • 【课堂笔记】复变函数-2
  • 25、优化算法与正则化技术:深度学习的调优艺术
  • qt QCategoryAxis详解
  • 云游戏时代,游戏盾如何保障新型业务的流畅体验
  • 【Block总结】LRSA,用于语义分割的低分辨率自注意力|TPAMI 2025