第4章:自动变量与模式规则
说实话,我刚接触Makefile那会儿,最头疼的就是写重复的规则。每个.c文件都要配一个.o规则,改一个文件名就得改好几处。后来我发现了自动变量和模式规则——嗯,这玩意儿简直就是Makefile的「语法糖」,用好了能让你的构建脚本瘦身80%。
4.1 自动变量:Makefile里的「代词」
自动变量是什么?说白了就是Makefile内置的「代词」。就像我们说话时用「它」代替「那个文件」一样,自动变量帮我们引用目标、依赖等元素。我个人习惯把这几个变量记成一组口诀:
| 自动变量 | 含义 | 我的记忆法 |
|---|---|---|
$@ |
当前规则的目标文件名 | 「@」像靶心,就是目标 |
$< |
第一个依赖文件 | 「<」像箭头,指向第一个 |
$^ |
所有依赖文件(去重) | 「^」像集合,包含全部 |
$? |
比目标新的依赖文件 | 「?」像问号,谁更新了? |
$* |
模式匹配的「主干」部分 | 「*」通配符,匹配的部分 |
核心要点:自动变量只能在规则的命令部分使用,不能在依赖列表里用。这是新手最容易踩的坑。
4.2 实战:从硬编码到自动变量
先看一个反面教材。我以前接手过一个项目,Makefile长这样:
main.o: main.c defs.h
gcc -c main.c -o main.o
utils.o: utils.c defs.h
gcc -c utils.c -o utils.o
net.o: net.c defs.h
gcc -c net.c -o net.o
你想想看,如果项目有50个.c文件,这得写到什么时候?而且改个编译器选项还得改50处。我用自动变量重构一下:
main.o: main.c defs.h
gcc -c $< -o $@
utils.o: utils.c defs.h
gcc -c $< -o $@
net.o: net.c defs.h
gcc -c $< -o $@
这里$<自动展开成第一个依赖(比如main.c),$@自动展开成目标(比如main.o)。代码量没少多少?别急,模式规则马上登场。
4.3 模式规则:用%匹配一切
模式规则里的%,你可以理解成「万能通配符」。它匹配任意字符串,然后把这个匹配到的值传给$*。上面的三个规则,用模式规则可以合并成一条:
%.o: %.c defs.h
gcc -c $< -o $@
就这一条规则,搞定了所有.c到.o的编译。Make会自动把%.o匹配到main.o时,%就是main,然后去查找main.c作为依赖。
我的小技巧:写模式规则时,我习惯先写最通用的规则,再写特例规则。因为Makefile采用「先匹配先使用」的原则。比如某个文件需要特殊编译选项,我会把特例规则写在前面。
4.4 自动变量组合拳
在实际项目中,我经常把多个自动变量组合使用。来看一个链接阶段的例子:
OBJS = main.o utils.o net.o
myapp: $(OBJS)
gcc $^ -o $@
%.o: %.c defs.h
gcc -c $< -o $@
这里$^展开成main.o utils.o net.o,$@展开成myapp。如果某个.o文件更新了,$?就能派上用场:
myapp: $(OBJS)
gcc $? -o $@
这样只有变更过的.o文件才会参与链接。我在做嵌入式项目时,编译一次要十几分钟,用$?做增量链接能省下不少时间。
4.5 避坑指南:我踩过的三个坑
坑一:在依赖列表里用自动变量
我曾经写过这样的代码:
# 错误写法!
%.o: $<.c
gcc -c $< -o $@
自动变量在依赖列表里是空的!正确做法是老老实实写%.o: %.c。
坑二:$^ 和 $+ 的区别
$^会去重,$+保留重复。有次我链接静态库时用了$^,结果某个库只出现了一次,链接失败。后来换成$+才搞定。
坑三:模式匹配的优先级
如果同时有%.o: %.c和debug_%.o: debug_%.c,Make会优先匹配更具体的模式。但如果你写了%.o: %.cpp和%.o: %.c,那就要小心了——Make会按顺序尝试,直到找到能匹配的规则。
4.6 知识体系总览
下面这张图是我自己总结的自动变量与模式规则的关系,你看完应该能一目了然:
4.7 综合示例:一个完整的构建系统
最后,我分享一个实际项目中用到的Makefile模板。它结合了自动变量、模式规则和条件判断:
CC = gcc
CFLAGS = -Wall -O2
LDFLAGS = -lm
SRCDIR = src
OBJDIR = obj
SRCS = $(wildcard $(SRCDIR)/*.c)
OBJS = $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(SRCS))
# 模式规则:编译
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
# 链接
myapp: $(OBJS)
$(CC) $^ -o $@ $(LDFLAGS)
# 增量编译:只重新编译修改过的文件
incremental: $(OBJS)
@echo "增量编译完成,修改的文件:$?"
$(CC) $? -o myapp $(LDFLAGS)
# 清理
clean:
rm -rf $(OBJDIR) myapp
你看,用了$@、$<、$^、$?,整个构建系统不到20行就搞定了。如果不用自动变量和模式规则,同样的功能至少得写50行,而且改起来还容易出错。
注意:使用$?做增量链接时,要确保所有依赖都正确声明。我曾经漏掉一个头文件依赖,结果改了头文件后Make没检测到变更,导致链接了旧的目标文件。排查了半天才发现是依赖列表不完整。
好了,自动变量和模式规则就讲到这里。记住一句话:能用模式规则的地方,就别写具体规则;能用自动变量的地方,就别写死文件名。这是我从无数次重构Makefile中总结出来的经验。
可可精品资料库