4. 自动变量:$@、$<、$^、$?、$* 的详细解析与实战
自动变量这东西,说白了就是 Makefile 里的「语法糖」。
我刚学 Makefile 那会儿,总觉得写规则就是「目标: 依赖」然后加个 Tab 缩进写命令。后来维护一个几十万行代码的老项目,发现每个 .o 文件都要手写编译命令,改一次头文件路径要改几十处……那叫一个痛苦。
直到我搞懂了自动变量,才明白为什么老鸟们的 Makefile 那么短、那么优雅。
4.1 什么是自动变量?
自动变量是 Make 在解析规则时,自动为我们填充的特殊变量。它们只在规则的命令部分生效,不需要你手动赋值。
你想想看,如果每次写规则都要重复写目标文件名、依赖文件名,那跟手写 shell 脚本有什么区别?自动变量就是来解决这个问题的。
核心思想:自动变量让规则变得「通用化」—— 一条规则模板,可以匹配成千上万个文件。
4.2 五大自动变量详解
| 变量 | 含义 | 典型场景 |
|---|---|---|
$@ |
规则的目标文件名 | 编译输出、链接输出 |
$< |
第一个依赖文件 | 单源文件编译 |
$^ |
所有依赖文件(去重) | 链接多个 .o 文件 |
$? |
比目标更新的依赖文件 | 增量编译、打包 |
$* |
目标文件的主干名(不含后缀) | 生成同名 .d 文件 |
4.3 $@ —— 目标文件名
这个变量最常用,也最好理解。它代表当前规则的目标。
# 编译单个源文件
main.o: main.c
gcc -c main.c -o main.o
# 用 $@ 改写
main.o: main.c
gcc -c $< -o $@
嗯,这里要注意:$@ 永远指向规则左边的那个目标。如果你写多条目标,它只取第一条。
我的习惯:只要规则里有输出文件,我一定用 $@。这样以后改目标名时,命令部分完全不用动。
4.4 $< —— 第一个依赖
这个变量代表依赖列表中的第一个文件。对于单源文件编译来说,它就是那个 .c 文件。
%.o: %.c
gcc -c $< -o $@
我在项目中遇到过一个问题:有人把 $< 和 $^ 搞混了。结果链接时只传了第一个 .o 文件,链接器报了一堆未定义引用。记住:$< 只取第一个,$^ 取全部。
4.5 $^ —— 所有依赖(去重)
这个变量展开后是所有依赖文件的列表,用空格分隔。而且 Make 会自动去重——同一个文件出现多次,只保留一次。
app: main.o utils.o io.o
gcc $^ -o $@
# 等价于
app: main.o utils.o io.o
gcc main.o utils.o io.o -o app
曾经踩过的坑:如果依赖中有重复文件,$^ 会自动去重。但如果你希望保留重复(比如某些特殊链接场景),要用 $+ 而不是 $^。不过说实话,我工作十年,几乎没用过 $+。
4.6 $? —— 更新的依赖
这个变量只包含那些「比目标文件新」的依赖。说白了,就是这次需要重新编译的文件。
# 只重新编译修改过的文件
lib.a: foo.o bar.o baz.o
ar rcs $@ $?
为什么会用到 $??想象一下:你改了 10 个文件中的 2 个,用 $? 打包时只处理这 2 个,效率高得多。
实战经验:我在做嵌入式固件项目时,每次编译都要打包几百个 .o 文件。用 $? 做增量归档,把打包时间从 30 秒降到了 2 秒。这种优化,说白了就是「只做必要的事」。
4.7 $* —— 主干名
这个变量有点特殊。它只在模式规则中有效,代表匹配 % 的那部分字符串。
# 生成依赖文件 .d
%.d: %.c
gcc -MM $< > $@
# 等价于
%.d: %.c
gcc -MM $*.c > $*.d
我个人习惯用 $* 来生成同名但不同后缀的文件。比如从 foo.c 生成 foo.d、foo.i、foo.s 等中间文件。
4.8 综合实战:一个完整的编译规则
CC = gcc
CFLAGS = -Wall -O2 -Iinclude
# 模式规则:编译 .c 到 .o
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 模式规则:生成依赖文件
%.d: %.c
@set -e; \
rm -f $@; \
$(CC) -MM $(CFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
# 链接
app: main.o utils.o io.o
$(CC) $^ -o $@
# 清理
clean:
rm -f *.o *.d app
你看,这个 Makefile 里几乎每个规则都用到了自动变量。没有它们,你得写多少重复代码?
4.9 自动变量使用原则
- 能用自动变量就别写死文件名 —— 这是 Makefile 优雅的第一原则
- 区分
$<和$^—— 单文件用前者,多文件用后者 $?只用于增量场景 —— 全量编译时用$^更清晰$*只在模式规则中用 —— 普通规则里它没有意义
一个小技巧:如果你不确定自动变量展开后是什么样子,可以在命令里加一行 @echo $@ $< $^ 来调试。我经常这么干。
4.10 知识体系图
这张图把五个自动变量的关系理清楚了。你写 Makefile 时,对照着这张图选变量就行。
自动变量是 Makefile 的基石。搞懂了它们,你写的规则会越来越短、越来越通用。下一节我们聊聊更高级的变量操作,但前提是——你得先把这五个变量用熟。
一句话总结:自动变量让 Makefile 从「手写脚本」变成了「声明式规则」。这是 Make 最优雅的设计之一。