4. 规则与目标
写 Makefile 的时候,最烦什么?我个人觉得,是重复。
你想想看,每个目标文件都要写一遍完整的编译命令,改个源文件名还得改规则。这种体力活,我干了三天就受不了了。后来我才发现,Make 早就给我们准备了一套「自动变量」,专门解决这种问题。
自动变量是什么?说白了,就是 Make 在匹配规则时自动填充的占位符。你不用手动指定文件名,Make 会根据当前正在处理的目标和依赖,自动帮你填进去。
核心思想:自动变量 = 让规则通用化,一份规则匹配 N 个文件。
4.1 五个最常用的自动变量
先看一张表,把这五个变量记住,你就能应付 90% 的场景。
| 变量 | 含义 | 典型值示例 |
|---|---|---|
$@ |
当前规则的目标文件名 | main.o |
$< |
第一个依赖文件 | main.c |
$^ |
所有依赖文件(去重) | main.c utils.c |
$? |
比目标新的依赖文件 | utils.c |
$* |
目标文件名去掉后缀的部分 | main |
嗯,这里要注意:$^ 会自动去重,而 $? 只保留「比目标新」的依赖。这两个特性在实际项目中非常有用。
4.2 实战场景一:编译单个源文件
这是最基础的应用。没有自动变量的时候,你得这么写:
main.o: main.c
gcc -c main.c -o main.o
utils.o: utils.c
gcc -c utils.c -o utils.o
每个文件写一遍,累不累?用自动变量,一行搞定:
%.o: %.c
gcc -c $< -o $@
这里 $< 自动变成 main.c 或 utils.c,$@ 自动变成 main.o 或 utils.o。模式匹配 + 自动变量,天生一对。
我的习惯:所有编译规则我都用 $< 和 $@ 写。这样即使以后改了源文件后缀,规则也不用动。
4.3 实战场景二:链接多个目标文件
链接的时候,依赖文件通常不止一个。这时候 $^ 就派上用场了。
app: main.o utils.o
gcc $^ -o $@
$^ 展开后是 main.o utils.o,不用你一个一个列出来。而且如果某个 .o 文件在依赖列表里出现了两次,$^ 会自动去重,不会导致链接错误。
我记得有一次,项目里因为头文件依赖写重复了,链接报了一堆符号重定义。排查了半天,最后发现是依赖列表里同一个 .o 写了两次。从那以后,链接规则我全用 $^,再没出过这种问题。
4.4 实战场景三:增量编译与 $?
$? 这个变量,我愿称之为「增量编译神器」。
它的值是「所有比目标新的依赖文件」。什么意思?就是只有那些被修改过的源文件对应的 .o,才会被重新编译。
libapp.a: main.o utils.o
ar rcs $@ $?
假设你只改了 utils.c,那么 $? 只包含 utils.o。打包静态库时,只更新这一个 .o 文件,效率极高。
我曾经踩过的坑:用 $? 做增量打包时,如果某个 .o 被删除了,$? 不会包含它。结果静态库里缺了文件,链接时符号找不到。所以 $? 适合「只增不改」的场景,全量打包还是用 $^ 更安全。
4.5 实战场景四:生成依赖文件与 $*
$* 代表目标文件名去掉后缀的部分。在生成 .d 依赖文件时特别好用。
%.d: %.c
gcc -MM $< -o $*.d
这里 $* 展开为 main、utils 等,生成的 .d 文件就是 main.d、utils.d。你想想看,如果没有 $*,你得用 $(basename ...) 函数去截取,多麻烦。
4.6 自动变量对比与选择
什么时候用哪个?我整理了一个决策表:
| 场景 | 推荐变量 | 原因 |
|---|---|---|
| 编译单个源文件 | $< + $@ |
依赖只有一个,$< 最直接 |
| 链接多个 .o | $^ + $@ |
自动去重,不会漏文件 |
| 增量打包静态库 | $? + $@ |
只更新修改过的文件,速度快 |
| 生成 .d 依赖文件 | $* + $< |
自动提取文件名前缀,省去字符串处理 |
| 复制/移动文件 | $< + $@ |
源和目标一一对应,简单明了 |
4.7 自动变量知识体系
下面这张图,帮你把自动变量的关系理清楚:
4.8 避坑指南
自动变量虽好,但有几个坑我不得不提:
- 不要在依赖列表里用自动变量——自动变量只在命令部分生效,依赖列表里写
$@是无效的。 - $^ 去重是好事也是坏事——如果你故意写了重复依赖(比如某些特殊场景),
$^会帮你「纠正」掉,可能不是你想要的。 - $? 只适用于增量场景——全量编译时别用,否则第一次构建可能漏文件。
- 模式规则中慎用 $*——如果目标文件名没有后缀(比如
lib),$*会变成空字符串,导致命令出错。
我的建议:刚开始用自动变量时,先加一行 @echo $@ $< $^ 看看展开结果。确认无误后再去掉调试信息。这个习惯帮我省了不少排查时间。
自动变量是 Makefile 从「手写脚本」走向「工程化构建」的第一步。掌握了它们,你的 Makefile 就能从几十行缩到几行,而且更健壮、更易维护。