Makefile精读 · M03-03 | 自动变量:`$@` `$<` `$^` `$?` `$*` 与目录变体
2026/7/23 3:21:17 网站建设 项目流程

模块: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.c

make 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.obuild/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)时更通用。

说明:

  1. 若目标无目录(app),$(@D).(当前目录)。
  2. 还有$(^D)$(^F)等对「所有依赖」的拆分,少见;认得即可。
  3. 老代码可能用$(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
  • $*是否出现在非模式规则中(可疑)

练习

判断题(对 / 错)

  1. $<表示所有依赖。
  2. 模式规则%.o: %.c中,更新foo.o$@foo.o$<foo.c
  3. 链接命令写成gcc -o $@ $?gcc -o $@ $^在任何情况下等价。
  4. $(@D)在目标为build/a.o时是build
参考答案
  1. 错。$<是第一个依赖;全部依赖是$^
  2. 对。
  3. 错。$?只含过期依赖,完整链接通常要用$^
  4. 对。

改错题

%.o: headers.h %.c gcc -c $< -o $@ app: main.o util.o gcc -o $@ $?

指出两处问题并改正。

参考答案
  1. $<会变成headers.h,应把%.c放在依赖首位。
  2. 链接应使用$^而不是$?
%.o: %.c headers.h gcc -c $< -o $@ app: main.o util.o gcc -o $@ $^

动手题

  1. 在模式规则配方第一行加@echo '$@ $< $^ $*', 构建两个.o观察输出。
  2. build/foo.o规则,打印$(@D)$(@F)
  3. 打开任意真实项目 Makefile,找到一处-o $@与一处-c $<,还原其具体文件。

下一篇预告

M03-04:《目标/模式特定变量、export与环境变量边界》—target: VAR := value、模式特定变量,以及 Make 变量如何进到配方里的 shell 环境。学完 M03-04,M03 模块收官,变量体系闭环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询