make 是一个构建工具;Makefile 是它读取的规则脚本,用来自动化编译、链接工程,避免重复敲长串 gcc 命令,解决依赖更新只重编改动文件。
一、基础原理
make 核心思想:时间戳比较
- 目标文件(target)和依赖文件(prerequisites)的修改时间对比
- 如果:目标不存在,或者依赖比目标新 → 执行命令(recipe)
- 如果:目标存在且所有依赖都比它旧 → 跳过,不执行
bash
# 语法模板
target : prerequisites
command
command 前面必须是 Tab,不能是空格!
示例:
bash
main: main.c hello.c
gcc main.c hello.c -o main
执行:
bash
make
# 指定目标
make main
# 清理
make clean
target(目标)& prerequisites(依赖)
一条 Makefile 规则的标准格式:
bash
target : prerequisites
command
中文:目标:依赖
后面的 command 叫做命令
- target 目标
目标:这条规则最终要生成的东西(或者一个动作),分两大类:
- 文件目标(普通目标) :是磁盘上真实存在的文件
- 例子:
main、main.o、hello.o - make 判断逻辑:对比这个文件的时间戳
- 例子:
- 伪目标(.PHONY) :不是文件,只是一个标签,代表一个动作
- 例子:
clean、all、install - 特点:不管有没有同名文件,只要被指定就执行命令
- 例子:
✅ 一句话:target 是"我想要得到什么"
bash
main: main.o hello.o
# target = main,我们最终要生成 main 可执行文件
- prerequisites 依赖(前置条件)
依赖:生成 target 需要的原料、前提文件,可以是多个,空格隔开;依赖本身也可以是另一条规则的 target。
main: main.o hello.o
# prerequisites = main.o hello.o
# 想要得到 main,必须先有 main.o 和 hello.o
make 会递归检查依赖 :如果 main.o 不存在或过时 → 先去找生成 main.o 的规则,先生成 main.o
✅ 一句话:prerequisites 是"生成目标需要依靠哪些东西"
- make 的核心判断逻辑(时间戳规则)
对于文件类型的 target:
如果满足下面任意一条 → 执行 command:① target 文件不存在;② 任意一个 prerequisites 的修改时间晚于 target 的修改时间(原料更新了,产物过时)
如果 target 存在,并且所有依赖都比它旧 → 跳过这条规则,不执行命令
举例子:
main: main.o hello.o
gcc main.o hello.o -o main
- 改了
main.c→main.o更新 →main.o比main新 → 重新链接 main - 只改注释,但是没重新编译
main.o→main.o时间没变 → 不重新链接 - 删掉 main → target 不存在 → 执行链接命令
伪目标不受时间戳判断!伪目标只要被选,就一定执行。
完整的 Makefile 示例(包含 main、main.o、hello.o 三条规则):
bash
main: main.o hello.o
gcc $^ -o $@
main.o: main.c
gcc -c main.c
hello.o: hello.c
gcc -c hello.c
依赖树:
main
├─ main.o
│ └─ main.c
└─ hello.o
└─ hello.c
执行 make main,make 自底向上检查。
- 容易踩坑:头文件默认不会被当成依赖
bash
main.o: main.c
gcc -c main.c
这里 prerequisites 只有 main.c,没有 main.h
你修改 main.h,make 看不到这个依赖,不会重新编译 main.o! 解决方案:-MMD 自动生成 .d 依赖文件,把头文件加入依赖列表。
- 自动变量和它们的对应关系(绑定 target /prerequisites)
假设规则:main: main.o hello.o
表格
| 变量 | 对应 |
|---|---|
$@ |
target → main |
$< |
第一个 prerequisite → main.o |
$^ |
所有 prerequisites(去重)→ main.o hello.o |
$? |
比 target 更新的那些 prerequisites |
| target(目标) | prerequisites(依赖) | |
|---|---|---|
| 含义 | 产出物 / 动作标签 | 原料、前置条件 |
| 文件目标 | 真实文件 | 源文件、中间 .o 文件 |
| 伪目标 | clean/all | 一般没有依赖,或者依赖其他目标 |
| make 判断 | 检查自己是否存在 / 是否过时 | 和目标对比时间戳 |
二、Makefile 四大组成
- 显式规则:上面这种,手写目标、依赖、命令
- 隐晦规则(内置隐式规则) :make 自带,比如
.c文件可以自动用gcc -c生成.o例:写main.o: main.c,不用写 gcc -c,make 自动补 - 变量定义
- 文件指示 + 注释 :
#注释;include引入其他 Makefile
三、变量(非常常用)
1. 变量赋值四种方式
bash
# = 递归赋值:展开时才取值,会向后找变量,容易循环
VAR = abc
:= 简单赋值:定义时立刻展开(推荐!)
VAR := abc
?= 条件赋值:变量未定义才赋值
VAR ?= abc
+= 追加赋值
VAR += def
2. 自动变量
表格
| 变量 | 含义 |
|---|---|
$@ |
当前规则的目标 |
$< |
第一个依赖文件 |
$^ |
所有依赖文件(去重) |
$? |
比目标新的依赖文件 |
$+ |
所有依赖文件(不去重) |
示例:
bash
main: main.o hello.o
gcc $^ -o $@
main.o: main.c
gcc -c $< -o $@
hello.o: hello.c
gcc -c $< -o $@
四、伪目标 .PHONY
问题:如果目录里刚好有名为
clean的文件,make clean会判断:clean 文件存在,不执行清理命令! 解决:.PHONY声明伪目标,告诉 make:这不是生成文件,只是一个动作,永远执行。
.PHONY: clean all
all: main
main: main.o hello.o
gcc $^ -o $@
clean:
rm -f *.o main
常用伪目标:all(编译全部)、clean、install、test
伪目标(Phony Target)
一句话:伪目标是一个标签,不是磁盘上的文件,作用只是用来执行一组命令。
普通目标:main、main.o → 对应真实文件,make 靠时间戳 判断要不要执行。 伪目标:clean、all、install、test → 不走时间戳判断,只要被指定就执行。
语法:.PHONY
.PHONY 是 make 的特殊内置目标,把它的依赖声明成伪目标:
bash
.PHONY: clean all
写在这一行后面的名字,全部标记为伪目标。
示例:
bash
.PHONY: clean
clean:
rm -f *.o main
如果不写 .PHONY: clean:
- 当目录里存在一个名叫 clean 的文件 / 文件夹
- clean 这个目标没有依赖,make 认为:目标已经存在、最新,输出
make: 'clean' is up to date.,不会执行删除命令
加上 .PHONY 之后: make 直接忽略同名文件,无条件执行 clean 的命令,不再对比时间戳。
| 普通目标(文件目标) | 伪目标 | |
|---|---|---|
| 本质 | 要生成的文件 | 动作标签,不生成文件 |
| 判断规则 | 比较 target 和 prerequisites 的时间戳 | 不看时间戳,无条件执行 |
| 声明 | 不需要 .PHONY | 需要加到 .PHONY: 后面 |
| 例子 | main、main.o | clean、all、install、run |
例子 main、main.o clean、all、install、run