本文为 Linux 开发工具专题的第四部分,详细讲解 Makefile 的编写规则、变量、自动变量、模式规则,以及如何编写一个功能完整的命令行进度条程序(涉及回车换行、缓冲区、刷新、格式化输出、旋转光标等知识)。通过理论与实践结合,帮助同学们掌握自动化构建和终端交互编程。
一、Makefile 入门与核心概念
1.1 什么是 Makefile?
-
make:一个命令,用于自动化编译。
-
Makefile:一个文件,定义了编译规则(依赖关系 + 依赖方法)。
1.2 第一个 Makefile 示例
假设只有一个源文件 myproc.c:
myproc: myproc.c gcc -o myproc myproc.c
-
myproc: myproc.c→ 依赖关系 :目标文件myproc依赖于myproc.c。 -
gcc -o myproc myproc.c→ 依赖方法 :如何从依赖生成目标(必须以 Tab 键开头,不能用空格)。
1.3 清理目标:clean 与伪目标 .PHONY
.PHONY: clean clean: rm -f myproc


-
.PHONY修饰的目标称为伪目标,它不表示一个真实文件。 -
伪目标的特点:总是执行其依赖方法,不受文件新旧影响。
为什么要有 .PHONY?
因为 clean 不是要生成一个叫 clean 的文件,而是执行删除操作。如果不加 .PHONY,当目录下意外存在一个名为 clean 的文件时,make clean 会认为目标已存在且没有依赖更新,从而不执行删除。
1.4 make 如何判断是否需要重新编译?
make 通过比较目标文件 和依赖文件 的 modify time(修改时间) 来决定是否执行依赖方法:
-
如果依赖文件的修改时间比目标文件新,则重新生成目标。
-
如果目标文件不存在,则肯定执行。
文件时间查看命令 :
stat 文件名文件有三种时间:
Access:最近访问时间(读取)
Modify:最近修改时间(内容改变)------ make 主要依据这个
Change:最近改变时间(属性改变)内容改变modify和change都会改变,属性改变只有change会变

1.5 为什么通常不用 .PHONY 修饰编译目标?
如果给 myproc 加上 .PHONY,那么每次 make 都会重新编译,即使源代码没有修改。这在大型项目中会浪费大量时间(只改了一个文件却要全部重编)。所以只对 clean 等不生成真实文件的目标使用 .PHONY。
二、Makefile 进阶:变量与自动变量
2.1 定义和使用变量
CC = gcc SRC = myproc.c BIN = myproc $(BIN): $(SRC) $(CC) -o $(BIN) $(SRC)
变量引用:
$(变量名)好处:修改一处,全局生效(例如更改编译器或目标名)
2.2 自动变量(简化依赖方法)
| 自动变量 | 含义 |
|---|---|
$@ |
表示目标文件 |
$^ |
表示所有依赖文件列表 |
$< |
表示第一个依赖文件 |
示例:
$(BIN): $(SRC) $(CC) -o $@ $^
2.3 模式规则:%.o: %.c
%.o: %.c $(CC) -c $< -o $@


-
%是Makefile下的通配符,在表示任意相同的字符串。 -
这条规则表示:如何从任意
.c文件生成同名的.o文件。 -
-c选项表示只编译不链接。
2.4 自动获取源文件列表
方法一:使用 wildcard 函数
SRC = $(wildcard *.c)
方法二:使用 shell 命令
SRC = $(shell ls *.c)
2.5 生成依赖的 .o 文件列表
OBJ = $(SRC:.c=.o)
- 这是一个替换引用 语法:将
SRC中所有.c后缀替换为.o。
2.6 一个通用的 Makefile 模板

-
这个 Makefile 可以自动处理当前目录下任意数量的
.c文件。 -
先编译每个
.c为.o,再链接所有.o生成可执行文件。
三、进度条程序编写
3.1 预备知识:回车与换行
-
换行(
\n):光标移动到下一行。 -
回车(
\r):光标移动到当前行的开头。
在终端中,我们通常想要的效果是:在同一行不断更新进度,这就需要使用 \r 让光标回到行首,然后覆盖之前的内容。
3.2 缓冲区与刷新
printf("hello"); // 没有 \n,数据暂存在缓冲区 sleep(3); // 此时屏幕上什么都没有 // 程序退出时自动刷新缓冲区,才显示 hello
-
标准输出(显示器)是行缓冲 模式:遇到
\n或缓冲区满或程序结束才会刷新。 -
如果想立即刷新,可以使用
fflush(stdout)。
3.3 倒计时程序(理解 \r 和 fflush)
#include <stdio.h> #include <unistd.h> int main() { int i = 10; while (i >= 0) { printf("%2d\r", i); // %2d 保证两位宽度,\r 回车覆盖 fflush(stdout); sleep(1); i--; } printf("\n"); return 0; }
-
%2d:固定输出两位数字,例如9,这样覆盖时不会留下残余字符。 -
fflush(stdout):立即把缓冲区内容输出到屏幕。
3.4 进度条的第一版(固定循环)
先创建好process.h、process.c、main.c、Makefile四个文件,
先编写Makefile

把预备工作做好后打开process,h

开始写核心代码部分process.c:

最后写main.c调用核心代码:

-
[%-100s]:左对齐,宽度 100,保证进度条区域固定。 -
每次增加一个
#,回车覆盖上一行,形成动态效果。
但是这个进度条是无法使用的,这个无法和网上的下载联动起来导致网上还没下好可能进度条就已经跑完了。
3.5 进度条的第二版(结合业务:下载模拟)
将进度条封装成函数,根据 已完成量 和 总量 动态计算进度百分比和 # 的个数。
//头文件
//源文件
-
static int pos:每次调用递增,模4得到光标字符,实现旋转效果。光标旋转与下载次数有关与下载速度无关。 -
注意:
printf内不要加\n,使用\r覆盖。
3.6 模拟下载的主程序
-
每次循环,current 增加一个较小的值,调用 FlushProcess更新进度条。
-
最终进度条会从 0% 逐渐走到 100%。
四、本节课总结
4.1 Makefile 核心要点
| 概念 | 说明 |
|---|---|
| 依赖关系 | 目标文件依赖于哪些源文件 |
| 依赖方法 | 如何从依赖生成目标(Tab 开头) |
伪目标 .PHONY |
总是执行,不检查文件新旧 |
| 变量 | CC = gcc,$(CC) 引用 |
| 自动变量 | $@(目标)、$^(所有依赖)、$<(第一个依赖) |
| 模式规则 | %.o: %.c |
| 文件时间 | make 根据 modify time 判断是否需要重新编译 |
| wildcard | 获取当前目录所有 .c 文件 |
| 替换引用 | $(SRC:.c=.o) 生成 .o 列表 |
4.2 进度条核心要点
| 知识点 | 说明 |
|---|---|
\r 与 \n |
\r 回车回到行首,\n 换行到下一行 |
| 缓冲区 | 行缓冲,fflush(stdout) 强制刷新 |
| 格式化宽度 | %2d、%-100s 固定宽度,避免覆盖残留 |
| 旋转光标 | 字符数组 -\|/ 循环显示,表示程序未卡死 |
| 进度计算 | 根据 current/total 算出百分比和 # 个数 |
4.3 最终进度条效果
[################### ][45.0%][\]

- 进度条、百分比、旋转光标三者同时更新,且在同一行。