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

Linux:make自动化和实战演练

Hello,小伙伴们!又到了咱们一起捣鼓代码的时间啦!💪 把生活调成热情模式,带着满满的能量钻进编程的奇妙世界吧——今天也要写出超酷的代码,冲鸭!🚀在这里插入图片描述
我的博客主页:喜欢吃燃面
我的专栏:《C语言》,《C语言之数据结构》,《C++》,《Linux学习笔记》
感谢你点开这篇博客呀!真心希望这些内容能给你带来实实在在的帮助~ 如果你有任何想法或疑问,非常欢迎一起交流探讨,咱们互相学习、共同进步,在编程路上结伴成长呀!

一.make与makefile

1. make和makefile的概念

  • 核心关联:能否编写Makefile,侧面反映是否具备完成大型工程的能力
  • Makefile作用:定义规则,指定文件编译顺序重编译需求复杂操作,实现“自动化编译”,写好后仅需make命令即可完成整个工程编译,大幅提升开发效率
  • 相关工具:make是解释Makefile指令命令工具,多数IDE均支持(如Delphi的make、Visual C++的nmake、Linux下GNU的make)。
  • 关系与目标:make命令Makefile文件,二者搭配实现项目自动化构建,成为工程编译的通用方法。
    注:习惯上我们将make命令执行的文件命名为Makefile。

2. make和Makefile的使用

我们先准备一个“”查找100以内素数“”的test.c文件

#include <stdio.h>int main() {printf("100以内的素数:\n");// 遍历2到100的所有数(1不是素数)for (int n=2; n<=100; n++) {int f=1;  // 1:默认是素数,0:不是// 用2到n/2的数试除,判断是否能整除for (int i=2; i<=n/2; i++)if (n%i == 0) {f=0; break;}  // 能整除则标记非素数并退出if (f) printf("%d ", n);  // 是素数则打印}putchar('\n');return 0;
}

然后我们创建Makefile文件。并配置成如下格式:

touch Makefile #创建文件
vim Makefile #编辑文件cat Makefile #查看Makefile内容
# 定义目标文件名
TARGET = test# 编译规则:默认目标为运行程序
all: run# 编译生成可执行文件
$(TARGET): $(TARGET).cgcc -o $(TARGET) $(TARGET).c# 运行程序
run: $(TARGET)./$(TARGET)# 清理编译生成的文件
clean:rm -f $(TARGET)

然后我们输入make run。test.c文件就被自动编译并运行了。如下:

make run
./test
100以内的素数:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 

3.配置Makefile文件的语法格式

我们以运行test.c的makefile文件为例,来分析语法:

3.1 定义目标文件名(定义变量)

# 定义目标文件名
TARGET = test

在 Makefile 里,TARGET = test 其实是给一个叫 TARGET 的“标签”起了个值叫 test

简单说,这个“标签”就像一个代号,后面在写编译规则的时候,不用反复写 test 这个具体名字,直接用 TARGET 这个代号就行。

比如要生成名为 test 的可执行文件,定义这个之后,后续所有涉及文件名的地方,都可以用 TARGET 代替。这样做的好处是,如果以后想把文件名改成别的(比如 app),只需要改这一行的 testapp,其他地方不用动,既方便又不容易出错,还能让整个文件的结构更清楚,一眼就知道 TARGET 代表的是最终要生成的那个文件。
类比一下:类似于C语言当中的宏替换

3.2 all: run 目标依赖规则

# 编译规则:默认目标为运行程序
all: run

在 Makefile 里,all: run 是一条“目标依赖”规则。

这里的 all 是一个特殊的目标(通常作为默认目标,即输入 make 命令时不指定目标就会执行它),而 run 是它所依赖的另一个目标。

意思就是:要完成 all 这个目标,必须先完成 run 这个目标。当执行 make all(或直接 make,因为 all 常作为默认目标)时,make 会先去检查并执行 run 对应的操作,等 run 完成后,all 才算完成。

这样设计的作用是把多个操作串起来,比如让 all 依赖 run,而 run 又依赖编译操作,就能实现“执行 make 就自动完成编译并运行程序”的效果,简化操作流程。

3.3 目标文件的依赖关系与依赖方法

# 编译生成可执行文件
$(TARGET): $(TARGET).cgcc -o $(TARGET) $(TARGET).c

这里引入新的名词: 依赖关系 依赖方法

  • 依赖关系:目标文件(如test)依赖于对应源文件(如test.c),源文件的存在或修改决定目标文件是否需要生成/更新。
  • 依赖方法:当依赖关系满足时,通过gcc编译器将源文件(如test.c)编译为目标文件(如test)的具体操作。
  • 自动执行:make工具会依据此规则,自动检查依赖并执行编译步骤。
  • 整个过程,简单来说,这行就是告诉 make 工具:想得到那个可执行文件,得先有对应的源文件;有了源文件后,就用 gcc 把它变成可执行文件。你运行 make 时,它会自动按这个逻辑来做。
    注:依赖关系和依赖方法必须具有合理性

解释:依赖关系和依赖方法必须具有合理性
比如你想做一碗番茄炒蛋(目标):

  • 依赖关系得合理:做番茄炒蛋,依赖的必须是番茄和鸡蛋(总不能依赖白菜和猪肉,那就不是番茄炒蛋了)。
  • 依赖方法也得合理:有了番茄和鸡蛋后,正确的做法是“番茄切块、鸡蛋打散,一起下锅翻炒”(总不能把番茄和鸡蛋直接丢进水里煮,那成了乱炖,不是番茄炒蛋的做法)。
  • 依赖关系和依赖方法必须对应起来:依赖的东西得是做这件事必需的,用这些东西的方法也得能真的做出目标结果——就像用番茄鸡蛋炒菜,才能得到番茄炒蛋,这就是合理性。

3.4 运行与清理操作的规则

# 运行程序
run: $(TARGET)./$(TARGET)

这行是说,要执行“run”这个操作,得先确保最终的可执行文件(比如test)已经生成好了。
等这个可执行文件准备好了,就会自动运行它(比如执行./test)。
简单讲,就是 “想运行程序,得先有程序;程序有了,就直接启动它” 。当你输入“make run”时,工具会按这个逻辑先检查程序是否存在,存在就马上运行。

# 清理编译生成的文件
clean:rm -f $(TARGET)

定义(类似C语言的宏定义)了一个叫“clean”的操作,作用是清理生成的文件。

意思是,当你执行“make clean”时,它会先确认要清理的目标(就是前面定义的那个可执行文件,比如test),然后用删除命令把这个文件删掉。

3.5 .PHONY关键字

在 Makefile 里,.PHONY 是一个特殊的关键字,用来声明“伪目标”。

作用
“伪目标”不是实际存在的文件,而是一个纯粹的操作指令(比如 allcleanrun 这些)。
.PHONY 声明后,Make 工具会明确:即便当前目录下有和伪目标同名的文件(比如叫 clean 的文件),执行 make clean 时也会忽略这个文件,直接执行 clean 对应的命令(比如删除文件)。

简单说
.PHONY 就是告诉 Make:“这些名字是操作指令,别当文件看待,不管有没有同名文件,都按我写的命令执行”,避免因同名文件导致操作失效。

比如:

.PHONY: all clean run

就是声明 allcleanrun 都是伪目标,确保 make allmake clean 等命令总能正常执行。

不推荐用 .PHONY 声明“实际生成文件的目标”
但“纯操作型目标”(如 clean、all、run)必须用 .
PHONY 声明——关键是区分目标的类型

核心原则:.PHONY 只用于“伪目标”,不用于“文件目标”

  • 文件目标:指最终会生成具体文件的目标(比如之前的 $(TARGET),会生成 test 可执行文件)。
    这类目标不能用 .PHONY 声明,因为 Make 的核心逻辑是“检查文件是否存在/更新”:如果声明为 .PHONY,会忽略文件的实际状态,每次执行都重新生成,一个文件如果反复被编译发出浪费时间,违背了 Make“增量编译”的初衷。
  • 伪目标:指不生成文件、只执行操作的目标(比如 clean、all、run)。
    这类目标必须用 .PHONY 声明,避免目录下有同名文件时,Make 误判“目标已完成”而跳过操作。

简单总结

  • 像 $(TARGET)(生成 test 文件)这样的“文件目标”:不声明 .PHONY(默认就是文件目标)。
  • 像 clean、all、run 这样的“操作型目标”:必须声明 .PHONY,这是规范且可靠的写法。

4.make的自动推导过程

Make 的“自动推导”(也叫“隐式规则”)是它的核心特性之一,简单说就是:Make 会默认知道一些常见的编译规则,不用你写完整命令,它能自动推导怎么生成目标文件

比如,当你要生成 test.o 这个目标文件时,即使你没写具体编译命令,Make 会自动判断:
“既然要生成 test.o,那很可能依赖 test.c 源文件,而且应该用 gcc -c test.c -o test.o 这个命令来编译”。

这个过程就是自动推导:

  1. 看到目标是 .o 结尾的文件(如 test.o),自动假设它依赖同名的 .c 文件(test.c);
  2. 自动使用 C 编译器(默认 ccgcc)的标准命令来编译,不用手动写编译步骤。

这样一来,你在 Makefile 里只需写清楚目标和依赖(比如 test.o: test.c),不用写具体编译命令,Make 会自己“猜”出该怎么做,大大简化了配置。

不止 .c 生成 .o,对 .cpp 生成 .o.o 链接成可执行文件等常见场景,Make 都有内置的自动推导规则,这也是它能高效处理编译流程的原因之一。

5.makefile的常用语法

BIN=proc.exe # 定义变量  
CC=gcc 
#SRC=$(shell ls *.c) # 采⽤shell命令⾏⽅式,获取当前所有.c⽂件名 
SRC=$(wildcard *.c) # 或者使⽤ wildcard 函数,获取当前所有.c⽂件名 
OBJ=$(SRC:.c=.o) # 将SRC的所有同名.c 替换 成为.o 形成⽬标⽂件列表 
LFLAGS=-o # 链接选项 
FLAGS=-c # 编译选项 
RM=rm -f # 引⼊命令 
$(BIN):$(OBJ) @$(CC) $(LFLAGS) $@ $^ # $@:代表⽬标⽂件名。 $^: 代表依赖⽂件列表 @echo "linking ... $^ to $@" 
%.o:%.c # %.c 展开当前⽬录下所有的.c。 %.o: 同时展开同名.o@$(CC) $(FLAGS) $< # $<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc。 @echo "compling ... $< to $@" # @:不回显命令 
.PHONY:clean 
clean:$(RM) $(OBJ) $(BIN) # $(RM): 替换,⽤变量内容替换它 .PHONY:test 
test: @echo $(SRC) @echo $(OBJ)

5.1 变量定义

  • BIN=proc.exe:指定最终生成的可执行文件名。
  • CC=gcc:指定使用的编译器为 gcc
  • SRC=$(wildcard *.c):通过 wildcard 函数获取当前目录下所有 .c 源文件。
  • OBJ=$(SRC:.c=.o):将所有 .c 源文件对应的目标文件名(.o 文件)整理成列表。
  • LFLAGS=-o:链接阶段的选项,用于指定输出文件。
  • FLAGS=-c:编译阶段的选项,用于生成目标文件(不进行链接)。
  • RM=rm -f:定义删除文件的命令,-f 表示强制删除,忽略不存在的文件。

5.2 编译与链接规则

  • 可执行文件生成($(BIN):$(OBJ)
    依赖所有 .o 目标文件,通过 gcc 链接这些目标文件生成可执行文件 proc.exe,并输出链接提示信息。
  • 目标文件生成(%.o:%.c
    利用模式匹配,为每个 .c 源文件生成对应的 .o 目标文件,编译时输出提示信息。

5.3 伪目标与辅助操作

  • clean 目标
    用于清理编译生成的所有 .o 目标文件和可执行文件 proc.exe,确保开发环境的整洁。
  • test 目标
    用于打印当前目录下的 .c 源文件列表和对应的 .o 目标文件列表,方便开发者检查文件匹配情况。

5.4 特殊符号说明

  • $@:代表目标文件名(如 proc.exe 或某个 .o 文件)。
  • $^:代表所有依赖文件的列表。
  • $<:代表第一个依赖文件(在模式匹配中用于逐个处理 .c 源文件)。
  • @:加在命令前,可隐藏命令本身的回显,仅显示自定义提示信息。

二.实战演练——进度条process

预备知识

first.回车与换行的区别

核心结论是:回车(CR)是光标回到行首,换行(LF)是光标下移一行,两者是独立操作,不同系统对“换行”的实现组合不同。

###1.核心概念区分

  • 回车(Carriage Return,CR):对应ASCII码的\r,作用是让光标从当前位置回到本行开头,不改变行的位置。
  • 换行(Line Feed,LF):对应ASCII码的\n,作用是让光标从当前位置下移一行,不改变列的位置

2.不同系统的换行实现

  • Windows系统:用\r\n(回车+换行)组合表示“换行”,先回行首再下移。
  • Unix/Linux/Mac(OS X及以后):用\n(仅换行)表示“换行”,系统会自动补全回车动作。

1.代码准备

1.1 process.h

#ifndef PROCESS_H
#define PROCESS_H#include <unistd.h>  // 提供usleep函数声明
#include <stdio.h>   // 提供printf等I/O函数声明extern char s[102];       // 声明进度条字符数组(全局变量)
void process(double _total);  // 声明进度条处理函数#endif

1.2 process.c

#include "process.h"
#include <stdlib.h>  // 提供rand、srand函数
#include <time.h>    // 提供time函数(用于随机数种子)// 定义全局变量(与头文件声明对应)
char s[102] = {0};
// 旋转光标序列(局部常量,仅在当前文件使用)
const char spinner[] = "|/-\\";// 进度条实现函数:模拟下载过程,显示进度
void process(double _total) {srand((unsigned int)time(NULL));  // 初始化随机数种子(按时间戳)double total = _total;            // 总下载量double current = 0.0;             // 当前已下载量double percent = 0.0;             // 下载百分比(保留两位小数)int spin_idx = 0;                 // 旋转光标索引// 循环模拟下载过程,直到完成while (current < total) {// 随机生成每次下载的增量(1~5的整数,转为double)double step = (double)(rand() % 5 + 1);current += step;// 避免超过总量(防止进度超过100%)if (current > total) {current = total;}// 计算百分比(精确到两位小数)percent = (current / total) * 100.0;// 更新进度条字符数组(用'='填充已完成部分)int bar_length = (int)percent;  // 进度条长度 = 百分比整数部分// 清空之前的进度条(避免残留字符)for (int i = 0; i < 100; i++) {s[i] = ' ';}// 填充已完成部分for (int i = 0; i < bar_length; i++) {s[i] = '=';}s[100] = '\0';  // 确保字符串结束符(避免越界)// 输出进度信息(\r表示回到行首,实现覆盖刷新)printf("[%-100s] [ %.1f / %.1f ] %.2f%% %c\r",s, current, total, percent, spinner[spin_idx]);fflush(stdout);  // 强制刷新缓冲区(确保实时显示)// 更新旋转光标索引(循环切换4个字符)spin_idx = (spin_idx + 1) % 4;usleep(20000);  // 休眠20ms(控制进度更新速度)}// 下载完成后,输出最终状态(换行避免覆盖)printf("[%-100s] [ %.1f / %.1f ] 100.00%% \n", s, total, total);printf("下载完成!\n\n");
}

1.3 main.c

#include "process.h"// 测试函数:调用不同总量的进度条
void test() {process(256);   // 测试总量256process(1024);  // 测试总量1024process(50);    // 测试总量50process(5000);  // 测试总量5000
}// 主函数:程序入口
int main() {test();return 0;
}

2.配置Makefile文件

# 编译器设置
CC = gcc
# 编译选项:启用警告,链接必要库(此处无需额外库)
CFLAGS = -Wall -Wextra# 目标可执行文件名
TARGET = progress_bar# 源文件列表
SRCS = main.c process.c# 生成目标
all: $(TARGET)# 编译可执行文件
$(TARGET): $(SRCS)$(CC) $(CFLAGS) $(SRCS) -o $(TARGET)# 运行程序
run: $(TARGET)./$(TARGET)# 清理编译产物
clean:rm -f $(TARGET)# 伪目标(避免与同名文件冲突)
.PHONY: all run clean

简单说明:

  • CC = gcc:指定使用gcc作为编译器。
  • CFLAGS = -Wall -Wextra:启用额外的警告信息(-Wall开启基本警告,-Wextra开启更多警告),有助于发现代码中的潜在问题。
  • TARGET = progress_bar:指定最终生成的可执行文件名为progress_bar
  • SRCS = main.c process.c:列出需要编译的源文件。
  • all: $(TARGET):默认目标,执行make时会编译生成可执行文件。
  • run: $(TARGET):自定义目标,执行make run可直接编译(若未编译)并运行程序。
  • clean: rm -f $(TARGET):清理目标,执行make clean会删除生成的可执行文件。
  • .PHONY: all run clean:声明伪目标,避免目录中存在同名文件时干扰Makefile的执行。

3.结果显示

===================================================================================================== [ 256.0 / 256.0 ] 100.00 % ? 
下载完成!
===================================================================================================== [ 1024.0 / 1024.0 ] 100.00 % ? 
下载完成!
===================================================================================================== [ 50.0 / 50.0 ] 100.00 % ? 
下载完成!
===================================================================================================== [ 5000.0 / 5000.0 ] 100.00 % ? 
下载完成!
http://www.dtcms.com/a/585037.html

相关文章:

  • Qt实战:自定义搜索跳转控件 | 附完整源码
  • nanochat大语言模型讲解一
  • Vue3:watch与watchEffect的异同
  • 做网站代理wordpress文章半透明
  • (论文速读)LyT-Net:基于YUV变压器的轻量级微光图像增强网络
  • 操作系统?进程!!!
  • Diffusion 到 Flow Matching ( 从 DDPM 到 Stable Diffusion ) 丝滑入门
  • 无监督学习与互信息
  • 数据集预处理:规范化和标准化
  • vue学习之组件与标签
  • 软件测试之bug分析定位技巧
  • Rust 练习册 :Pig Latin与语言游戏
  • Tomcat的基本使用作用
  • 完整网站建设教程网站建设需要会什么软件
  • 【ASP.Net MVC 】使用Moq让单元测试变得更简单
  • Linux:线程的概念与控制
  • 零基础学AI大模型之嵌入模型性能优化
  • 【二叉搜索树】:程序的“决策树”,排序数据的基石
  • Canvas/SVG 冷门用法:实现动态背景与简易数据可视化
  • 昆明做网站做的好的公司智能建站系统 网站建设的首选
  • kali安装npm/sourcemap
  • 协作机器人的关节是通过什么工艺加工的
  • 轻松开启数字化时代,一键部署实现CRM落地
  • 长春市网站推广网站开发技术人员
  • JavaScript 指南
  • C++ LeetCode 力扣刷题 541. 反转字符串 II
  • C++死锁深度解析:从成因到预防与避免
  • 达梦DMDSC知识
  • 【C++】基于C++的RPC分布式网络通信框架(二)
  • Python 实现:从数学模型到完整控制台版《2048》游戏