4. 自动变量与模式规则:让 Makefile 从“写死”变成“通用”
上一章我们还在手动为每个 .c 文件写编译命令,对吧?说实话,那会儿我看着满屏重复的规则,心里就一个念头——这跟复制粘贴有啥区别?
这一章,我们来解决这个问题。核心就两个东西:自动变量 和 模式规则。搞懂了它们,你的 Makefile 就能从“写死”变成“通用”。
4.1 自动变量:Makefile 里的“代词”
先想想看,我们写规则时,最烦的是什么?是重复写目标文件名、依赖文件名。自动变量就是干这个的——它们像代词一样,自动指代当前规则中的特定部分。
我列几个最常用的,你记一下:
| 自动变量 | 含义 | 举个例子(规则 target: a.o b.o) |
|---|---|---|
$@ |
当前规则的目标文件 | 就是 target |
$< |
第一个依赖文件 | 就是 a.o |
$^ |
所有依赖文件(去重) | 就是 a.o b.o |
$? |
所有比目标新的依赖文件 | 如果 b.o 比 target 新,就是 b.o |
$* |
模式匹配中的“茎”(stem) | 后面讲模式规则时会用到 |
来看个实际例子。以前我们写编译规则是这样的:
main.o: main.c
gcc -c main.c -o main.o
utils.o: utils.c
gcc -c utils.c -o utils.o
用了自动变量后,变成这样:
main.o: main.c
gcc -c $< -o $@
utils.o: utils.c
gcc -c $< -o $@
你看,命令部分完全一样了。$< 自动变成 main.c 或 utils.c,$@ 自动变成 main.o 或 utils.o。这就是“通用化”的第一步。
4.2 模式规则:用 % 匹配一类文件
自动变量解决了命令部分的重复,但规则头(target: dependency)还是得一个个写。模式规则就是来解决这个的。
模式规则的核心是 % 通配符。它匹配文件名中的任意部分。比如 %.o: %.c 的意思是:所有 .o 文件都依赖于同名的 .c 文件。
有了模式规则,上面的例子可以写成一行:
%.o: %.c
gcc -c $< -o $@
就这么一行,就能匹配项目中所有的 .c 文件。Make 会自动找到每个 .c 文件,生成对应的 .o 文件。
%.o: %.c 能匹配 src/main.o 和 src/main.c 吗?不能!因为 % 匹配的是 src/main,但 Make 会把路径也当作匹配的一部分。如果你有子目录,需要用 src/%.o: src/%.c 这种形式。
4.3 实战:用自动变量 + 模式规则重写编译规则
好,理论讲完了,我们直接上实战。假设项目结构是这样的:
project/
├── main.c
├── utils.c
├── data.c
└── Makefile
用我们刚学的知识,写一个通用的 Makefile:
CC = gcc
CFLAGS = -Wall -g
# 目标文件列表
OBJS = main.o utils.o data.o
# 最终目标
app: $(OBJS)
$(CC) $^ -o $@
# 模式规则:所有 .o 都从 .c 编译而来
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理
clean:
rm -f *.o app
这个 Makefile 已经相当通用了。你只需要修改 OBJS 列表,就能适配不同的项目。
但说实话,每次有新文件都要手动加到 OBJS 里,还是有点烦。有没有更自动化的办法?
4.4 更进一步:自动获取源文件列表
我们可以用 Make 的内置函数来自动获取源文件列表。比如:
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
$(wildcard *.c) 会展开成当前目录下所有 .c 文件的列表。$(SRCS:.c=.o) 会把列表中的 .c 后缀替换成 .o。
这样,你新增一个 .c 文件,Makefile 完全不用改。是不是很爽?
完整的 Makefile 变成这样:
CC = gcc
CFLAGS = -Wall -g
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
TARGET = app
$(TARGET): $(OBJS)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
4.5 知识体系:自动变量与模式规则的关系
为了让你更直观地理解它们之间的关系,我画了张图:
4.6 避坑指南:我踩过的几个坑
最后,分享几个我实际项目中踩过的坑,你遇到了能少走弯路:
- 模式规则中的 % 不能匹配路径分隔符 —— 如果你有子目录,比如
src/main.c,用%.o: %.c是匹配不到的。需要用src/%.o: src/%.c或者用vpath指令。 - $^ 会去重 —— 如果同一个文件被多次列出,$^ 只会保留一个。这通常是好事,但如果你需要保留重复项(比如链接时),要用
$+。 - 自动变量只在规则的命令部分生效 —— 你不能在依赖列表里用 $@ 或 $<。比如
$@: $^.c这种写法是错的。 - 小心通配符展开时机 ——
$(wildcard *.c)是在 Makefile 解析时展开的。如果你在规则中动态生成 .c 文件,它可能不会包含新文件。这时候需要用$(shell ...)函数。
make -p 查看 Make 内部解析后的规则,看看它是不是按你预期的方式工作的。这招帮我发现过不少隐藏问题。