
大家好,欢迎来到 huangjin007_ 的博客
⭐
个人主页:huangjin007_
🔥
文章收录专栏:Linux 内功修炼手册(系统篇)
总会有一些坚持能从冰封的土地里培育出十万朵怒放的蔷薇
Linux 系统篇(九) —— 自动化构建 make/Makefile 详解
文章目录
- Linux 系统篇(九) —— 自动化构建 make/Makefile 详解
-
- 一、为什么要学 Make 和 Makefile?
-
- 1.1 手动编译
- 1.2 Make 的核心思想:增量编译
- 二、生活化的例子
-
- 2.1 月底要钱的例子
- 2.2 翻译成 Makefile 的思维
- 三、第一个 Makefile
-
- 3.1 准备源代码
- 3.2 手写一个最简单的 Makefile
- 3.3 分析这个 Makefile 的结构
- 3.4 运行一下
- 3.5 清理:`clean` 与 `.PHONY`
- 四、文件的修改时间
-
- 4.1 stat 命令
- 4.2 Make 如何利用 Modify 时间?
- 五、Make 的工作方式与依赖链
-
- 5.1 一个展示编译全过程的 Makefile
- 5.2 Make 的工作步骤
- 六、扩展语法
-
- 6.1 变量定义
- 6.2 自动获取源文件
- 6.3 模式替换
- 6.4 自动化变量
- 6.5 命令回显控制
- 6.6 综合示例
- 结语:
一、为什么要学 Make 和 Makefile?
1.1 手动编译
如果你在做一个 C 语言项目,刚开始只有一个源文件 main.c,编译很简单:
gcc main.c -o app
过两天项目扩展了,增加了 util.c、net.c、db.c……你不得不这么编译:
gcc -c main.c -o main.o
gcc -c util.c -o util.o
gcc -c net.c -o net.o
gcc -c db.c -o db.o
gcc main.o util.o net.o db.o -o app
如果某个头文件改了,或者你在 util.c 里修了一个小 bug,难道要把所有文件都重新编译一遍?这太浪费时间了。
那能不能只编译那些真正发生变化的部分?
1.2 Make 的核心思想:增量编译
make 会根据文件的 修改时间 自动判断哪些文件需要重新编译。它的逻辑很简单:
- 每个目标文件(比如 .o)都依赖某些源文件(比如 .c )。
- 如果依赖文件的“最后修改时间”比目标文件新,说明源文件改过了,目标文件需要重新生成。
- 否则,就不需要动它。
这样一来,每次你只改了一个文件,make 就只重新编译受影响的部分,效率就能得到提高。
二、生活化的例子
2.1 月底要钱的例子
你是一个大学生,每个月月底都要向爸妈要生活费。这个行为可以描述为:
- 目标:拿到生活费。
- 依赖:爸妈的工资到账。
- 规则:如果工资已到账,就打电话要钱。
你发现规律了吗? 如果工资一直不到账(依赖不满足),要钱的动作就不会发生。如果上个月的生活费还没花完(目标已存在且比依赖新),你也可能不急着要。但如果你声明“不管怎样,每个月必须执行一次要钱操作”,那就类似于 Makefile 中的 伪目标 .PHONY。
2.2 翻译成 Makefile 的思维
生活费: 爸妈工资到账
打电话要钱
- 冒号前面是目标,后面是依赖。
- 下一行缩进(必须用 Tab 键!)是达成目标需要执行的命令。
make 会检查“爸妈工资到账”这个依赖的状态,然后决定是否执行“打电话要钱”。
三、第一个 Makefile
3.1 准备源代码
创建一个 myproc.c:
#include <stdio.h>
int main()
{
printf("hello Makefile!\\n");
return 0;
}
3.2 手写一个最简单的 Makefile
新建一个名为 Makefile 或 makefile 的文件,内容如下:
myproc: myproc.c
gcc -o myproc myproc.c
.PHONY: clean
clean:
rm -f myproc
3.3 分析这个 Makefile 的结构
每一个构建规则都由三部分组成:
| 目标 | 你想生成的文件,或者是一个动作的名字 | myproc、clean |
| 依赖 | 生成目标需要的原材料 | myproc.c |
| 命令 | 如何用依赖生成目标,必须以 Tab 开头 | gcc -o myproc myproc.c |
整个逻辑就是:如果目标文件不存在,或者任何一个依赖文件比目标文件新,就执行下面的命令。
3.4 运行一下
在终端输入:
make
输出:
gcc -o myproc myproc.c
可执行文件 myproc 生成了。再执行一次 make,会提示:
make: 'myproc' is up to date.
因为 myproc 已经存在,而且它的修改时间比 myproc.c 新,不需要重新编译。
3.5 清理:clean 与 .PHONY
项目开发过程中会产生很多中间文件(.o、.i、.s 等)和最终可执行文件,我们需要一个清理动作。上面的 Makefile 里定义了 clean 目标:
clean:
rm -f myproc
执行 make clean,就会删除 myproc。
但有个陷阱:如果当前目录下恰好有一个文件也叫 clean,那么 make clean 就傻掉了。 它会认为目标 clean 已经存在且依赖为空,无需更新,于是什么都不做。
用 .PHONY 修饰后:
.PHONY: clean
这样 clean 就成为一个“伪目标”,无论同名的文件是否存在,它后面的命令都会无条件执行。这也就是为什么“总是被执行”的意思。
四、文件的修改时间
4.1 stat 命令
在 Linux 中,文件由“内容”和“属性”两部分组成。使用 stat 命令可以查看文件的时间戳:
stat myproc.c
输出示例: 
这里有三个时间:
- Access(访问时间):文件最近一次被读取的时间。
- Modify(内容修改时间):文件内容最后一次被修改的时间。这是 make 最关心的,也是判断是否重新编译的根本依据。
- Change(属性修改时间):文件元数据(权限、拥有者等)最后一次被改变的时间。
4.2 Make 如何利用 Modify 时间?
假设现在有 myproc 和 myproc.c 两个文件:
- 如果 myproc 不存在,那肯定要编译。
- 如果 myproc 存在,但 myproc.c 的 Modify 时间比 myproc 的 Modify 时间更新,说明源代码改过了,需要重新编译。
- 如果 myproc.c 的时间更旧,则无需重编。
记住结论:.PHONY 会让 make 完全忽略文件修改时间的对比,强制执行命令。
五、Make 的工作方式与依赖链
5.1 一个展示编译全过程的 Makefile
回顾 C 语言编译的四个步骤:预处理 -> 编译 -> 汇编 -> 链接,对应生成 .i、.s、.o 和最终可执行文件。我们可以写一个能展示完整流程的 Makefile:

执行 make,输出:
gcc -E myproc.c -o myproc.i
gcc -S myproc.i -o myproc.s
gcc -c myproc.s -o myproc.o
gcc myproc.o -o myproc
神奇的是,我们只写下了目标和依赖,make 就自动按顺序一步步生成了所有文件。
5.2 Make 的工作步骤
在默认方式下(只输入 make 命令),make 的运作流程如下:
这个过程可以总结为:自上而下解析依赖关系,自下而上执行构建命令。
六、扩展语法
6.1 变量定义
Makefile 中可以用 变量名=值 来定义变量,引用时用 $(变量名) 或 ${变量名}。例如:

这样修改编译器或输出文件名时,只改变量定义即可。
6.2 自动获取源文件
wildcard 函数可以匹配文件名:
SRC=$(wildcard *.c)
这会把当前目录下所有 .c 文件赋值给 SRC。
6.3 模式替换
有了源文件列表,我们还需要得到对应的目标文件列表(.o 文件)。可以用模式替换:
OBJ=$(SRC:.c=.o)
这里 $(SRC:.c=.o) 的意思是把 SRC 中所有以 .c 结尾的字符串,替换成以 .o 结尾。假设 SRC 是 main.c util.c,那么 OBJ 就是 main.o util.o。
6.4 自动化变量
Makefile 提供了一些特殊的变量,在规则中非常实用:
- $@ :代表当前规则中的目标文件名。
- $< :代表当前规则的第一个依赖文件名。 当只有单个依赖时,$< 就是指那个源文件 myproc.c。
- $^ :代表当前规则的所有依赖文件列表(以空格分隔)。 如果依赖有多个,$^ 会展开为 file1.c file2.c …
举个简单的例子:
$(BIN): $(OBJ)
gcc $^ -o $@
假如 BIN=app,OBJ=main.o util.o,那么实际执行的命令就是:
gcc main.o util.o -o app
再比如模式规则:
%.o: %.c
gcc -c $< -o $@
%.o: %.c 表示:对于任何要生成的 .o 文件,它的依赖是对应的同名 .c 文件。$< 在这里代表第一个依赖,也就是那个 .c文件;$@ 就是目标 .o 文件。
对于 main.o,$< 是 main.c,$@ 是 main.o。这样一条规则就能处理所有 .c 到 .o 的编译。
6.5 命令回显控制
默认情况下,make 会把执行的命令打印到屏幕上。如果不想显示命令本身(例如为了输出更清晰),可以在命令前加 @:
@echo "正在编译…"
这样屏幕只会显示 正在编译…,而不会打印 echo 命令本身。
6.6 综合示例
现在我们把以上特性整合起来,得到一个相对完整的工程 Makefile:

解读:
- 变量区:把所有可配置项放在最前面,方便修改。
- 最终目标 $(BIN): $(OBJ):可执行文件依赖所有 .o 文件,链接命令使用自动化变量。
- 模式规则 %.o: %.c:这是 make 的隐式规则骨架,告诉 make 任何一个 .o 文件都可以通过同名的 .c 文件编译得到。$< 代表被编译的那个 .c 文件。
- clean:清理所有生成文件,伪目标,确保一定会执行。
结语:
今天的内容到这里就结束了,希望你能有所收获~
干货整理到手抖,觉得有用的话,赏个三连回回血?__(:ᗤ」ㄥ)_ _





