模块:M03 变量体系
篇号:M03-03 / 41
预计阅读:40~45 分钟
主线:GNU Make
加厚篇★
文章目录
- 本篇目标
- 30 秒速览
- 正文
- 1. 自动变量是什么?
- 2. 核心五件套
- 2.1 `$@` — 当前目标
- 2.2 `$<` — 第一个依赖(prerequisite)
- 2.3 `$^` — 所有依赖,去重
- 2.4 `$+` — 所有依赖,保留重复
- 2.5 `$?` — 「过期相关」的依赖
- 2.6 `$*` — 茎(stem)
- 3. 一张表背下来
- 4. 目录与文件名变体
- 5. 和模式规则一起读(加厚)
- 6. 常见坑
- 6.1 在配方外使用
- 6.2 `$<` 不是「唯一源文件」,而是「第一个依赖」
- 6.3 `$^` 包含不想链进的文件
- 6.4 混淆 `$?` 与 `$^`
- 6.5 `$*` 迷信
- 7. 实战对照:手写名 vs 自动变量
- 8. 次常用:`$|`、`$%`(扫盲)
- 9. 对照表(可贴显示器)
- 读 Makefile 检查清单
- 练习
- 判断题(对 / 错)
- 改错题
- 动手题
- 下一篇预告
本篇目标
吃透规则配方里最常见、却从不需要你赋值的符号:自动变量。掌握$@$<$^$?$*的精确含义,以及$(@D)/$(@F)等目录变体。学完本篇,模式规则与链接行终于能读能写,不再靠「抄别人 Makefile」蒙混。
30 秒速览
- 自动变量只在配方中有意义,由 Make 在执行该规则时填入。
$@:当前目标;$<:第一个依赖;$^:所有依赖(去重);$?:比目标新的依赖;$*:模式规则的茎。- 链接常用:
$(CC) -o $@ $^;编译常用:$(CC) -c $< -o $@。 - 目录变体:
$(@D)/$(@F)目标的目录/文件名;$^亦有$(^D)等(少见但要认得)。 $*在非模式规则里常为空或不靠谱——茎相关逻辑优先信任模式/静态模式。- 写死文件名能跑,但换目标就炸;自动变量让一条规则服务一类文件。
正文
1. 自动变量是什么?
它们不是你在文件开头:=出来的配置,而是 Make 在执行某条规则的配方时临时提供的:
app: main.o util.o gcc -o $@ $^执行更新app时:
| 自动变量 | 值 |
|---|---|
$@ | app |
$^ | main.o util.o |
$< | main.o(第一个) |
没有自动变量时你得写:
gcc -o app main.o util.o一换目标名或依赖列表,配方就要改——模式规则更是几乎离不开自动变量。
作用域记忆:自动变量为「当前正在执行的这条规则」服务;写在配方外通常无意义(值为空或不符合预期)。
2. 核心五件套
2.1$@— 当前目标
hello: hello.c gcc -o $@ hello.cmake hello→-o hello。
模式规则里更关键:一条配方生成「正在构建的那个文件」。
2.2$<— 第一个依赖(prerequisite)
%.o: %.c gcc -c $< -o $@| 目标 | $< | $@ |
|---|---|---|
main.o | main.c | main.o |
编译规则几乎总是$<→ 源文件,$@→ 目标文件。
若依赖写成%.c common.h,$<仍是第一个,即对应的.c,不会变成common.h(顺序很重要)。
2.3$^— 所有依赖,去重
app: main.o util.o main.o gcc -o $@ $^$^是main.o util.o(重复的main.o去掉)。
链接行圣经:
$(CC) -o $@ $^2.4$+— 所有依赖,保留重复
与$^类似但不去重。少数工具链对链接顺序/重复库敏感时可能用到。一般优先$^。
2.5$?— 「过期相关」的依赖
比目标更新的那些依赖(目标不存在时,通常是全部依赖)。适合「只处理变化的输入」:
lib.a: a.o b.o c.o ar r $@ $?含义直觉:只把比lib.a新的.o更新进库。
日常编译/链接更常用$^;读旧库维护规则时会见到$?。
2.6$*— 茎(stem)
在模式规则%.o: %.c更新path/foo.o时,$*为path/foo。
%.o: %.c @echo stem=$* gcc -c $< -o $@显式规则(无模式)里$*常常为空——不要在普通hello: hello.c上依赖$*。需要茎时,用模式/静态模式。
3. 一张表背下来
对规则:
build/app: main.o util.o假设正在更新build/app,且util.o比build/app新、main.o不新:
| 变量 | 典型值 | 口诀 |
|---|---|---|
$@ | build/app | 目标全家桶路径 |
$< | main.o | 第一个依赖 |
$^ | main.o util.o | 所有依赖(去重) |
$+ | 同$^(本例无重复) | 所有依赖(保留重复) |
$? | util.o | 比目标新的依赖 |
$* | (非模式时勿用) | 模式茎 |
编译模式:
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@链接显式:
app: $(OBJS) $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)4. 目录与文件名变体
目标或依赖带路径时,常用「拆目录 / 拆文件名」:
| 变量 | 含义 | 若$@=build/tmp/app |
|---|---|---|
$(@D) | 目标的目录部分 | build/tmp |
$(@F) | 目标的文件名部分 | app |
$(<D)/$(<F) | 第一个依赖的目录/文件名 | 同理 |
$(*D)/$(*F) | 茎的目录/文件名 | 模式规则时 |
经典用途:确保输出目录存在:
build/%.o: src/%.c mkdir -p $(@D) $(CC) -c $< -o $@也可:
mkdir -p build但$(@D)在多层子目录(build/foo/bar.o)时更通用。
说明:
- 若目标无目录(
app),$(@D)为.(当前目录)。 - 还有
$(^D)、$(^F)等对「所有依赖」的拆分,少见;认得即可。 - 老代码可能用
$(dir $@)/$(notdir $@)函数(M04)达到类似效果。
5. 和模式规则一起读(加厚)
bin/%: src/%.c mkdir -p $(@D) $(CC) $(CFLAGS) -o $@ $<make bin/hello时:
| 变量 | 值 |
|---|---|
$@ | bin/hello |
$< | src/hello.c |
$* | hello |
$(@D) | bin |
静态模式同样适用:
$(OBJS): build/%.o: src/%.c mkdir -p $(@D) $(CC) -c $< -o $@阅读口诀:先定当前目标 → 填$@→ 看规则依赖列表如何展开 → 填$</$^→ 若有%再填$*。
6. 常见坑
6.1 在配方外使用
TARGET := $@ # 通常得不到「某个规则的目标」解析阶段没有「当前规则目标」的完整上下文,别这么存。
6.2$<不是「唯一源文件」,而是「第一个依赖」
%.o: common.h %.c gcc -c $< -o $@$<是common.h,编译会错。应把源文件依赖写在最前:
%.o: %.c common.h gcc -c $< -o $@6.3$^包含不想链进的文件
若把头文件或生成脚本误列进依赖,$^会把它们丢给链接器。链接依赖应只列.o/库;头文件用附加依赖规则挂上(M02-02 / M06)。
6.4 混淆$?与$^
$?只含过期依赖;完整链接一般需要$^,否则可能漏掉未重编的.o(它们仍应参与链接)。ar r $@ $?是库更新的特殊习惯,不要照搬到gcc -o $@ $?。
6.5$*迷信
非模式规则里打印$*为空,不代表 Make 坏了。需要「去后缀名」时,用$(basename $@)或模式茎。
7. 实战对照:手写名 vs 自动变量
# 僵硬 app: main.o util.o gcc -o app main.o util.o # 灵活 app: main.o util.o gcc -o $@ $^ # 再进一步 OBJS := main.o util.o app: $(OBJS) gcc -o $@ $^改OBJS时配方不用动——这就是自动变量的工程价值。
8. 次常用:$|、$%(扫盲)
| 变量 | 含义 |
|---|---|
$| | order-only 依赖(M06-03);不参与「是否过期」的时间戳比较 |
$% | 当目标是archive(member)形式时的成员名;现代少手写 |
本篇以五件套 + 目录变体为主;见到$|知道去 M06 即可。
9. 对照表(可贴显示器)
$@ 目标 $< 首依赖 $^ 全依赖(去重) $+ 全依赖(保留重复) $? 比目标新的依赖 $* 模式茎 $(@D) $(@F) 目标的目录 / 文件名编译:-c $< -o $@
链接:-o $@ $^
建目录:mkdir -p $(@D)
读 Makefile 检查清单
- 配方里的
$@$<$^能否还原成具体文件名 $<对应的是否真是「源文件」(依赖顺序对不对)- 链接行用的是
$^还是误用$? - 带输出目录时是否用
$(@D)/mkdir -p $*是否出现在非模式规则中(可疑)
练习
判断题(对 / 错)
$<表示所有依赖。- 模式规则
%.o: %.c中,更新foo.o时$@为foo.o,$<为foo.c。 - 链接命令写成
gcc -o $@ $?与gcc -o $@ $^在任何情况下等价。 $(@D)在目标为build/a.o时是build。
- 错。
$<是第一个依赖;全部依赖是$^。 - 对。
- 错。
$?只含过期依赖,完整链接通常要用$^。 - 对。
改错题
%.o: headers.h %.c gcc -c $< -o $@ app: main.o util.o gcc -o $@ $?指出两处问题并改正。
参考答案$<会变成headers.h,应把%.c放在依赖首位。- 链接应使用
$^而不是$?。
%.o: %.c headers.h gcc -c $< -o $@ app: main.o util.o gcc -o $@ $^动手题
- 在模式规则配方第一行加
@echo '$@ $< $^ $*', 构建两个.o观察输出。 - 写
build/foo.o规则,打印$(@D)$(@F)。 - 打开任意真实项目 Makefile,找到一处
-o $@与一处-c $<,还原其具体文件。
下一篇预告
M03-04:《目标/模式特定变量、export与环境变量边界》—target: VAR := value、模式特定变量,以及 Make 变量如何进到配方里的 shell 环境。学完 M03-04,M03 模块收官,变量体系闭环。