Makefile精读 · M03-03 | 自动变量:`$@` `$<` `$^` `$?` `$*` 与目录变体
模块M03 变量体系篇号M03-03 / 41预计阅读4045 分钟主线GNU Make加厚篇★文章目录本篇目标30 秒速览正文1. 自动变量是什么2. 核心五件套2.1 $ — 当前目标2.2 $ — 第一个依赖prerequisite2.3 $^ — 所有依赖去重2.4 $ — 所有依赖保留重复2.5 $? — 「过期相关」的依赖2.6 $* — 茎stem3. 一张表背下来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.omain.cmain.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-04M03 模块收官变量体系闭环。