4. 隐式规则与模式匹配
说实话,我刚接触Makefile那会儿,最头疼的就是重复写规则。每个.c文件都要配一个.o规则?那项目大了还不得写到天荒地老?后来我才发现,Make早就替我们想好了——隐式规则和模式匹配,就是干这个的。
4.1 隐式规则:Make替你写好的规则
什么叫隐式规则?说白了就是Make内置的一套“潜规则”。你不需要告诉它怎么把.c变成.o,它自己就知道。
举个例子,你写个最简单的Makefile:
hello: hello.o
gcc -o hello hello.o
然后你目录里有个hello.c。你执行make,猜猜会发生什么?
Make会自动调用 gcc -c hello.c -o hello.o。这就是隐式规则在起作用。
核心要点:隐式规则只对标准后缀名有效。.c → .o,.cpp → .o,.f → .o,这些都是Make认识的“老熟人”。
4.2 隐式规则列表:Make认识哪些套路?
我整理了一份常用的隐式规则表,你在项目里大概率会用到:
| 源文件后缀 | 目标文件后缀 | 默认命令 |
|---|---|---|
| .c | .o | $(CC) -c $(CFLAGS) $< -o $@ |
| .cpp / .cc | .o | $(CXX) -c $(CXXFLAGS) $< -o $@ |
| .f | .o | $(FC) -c $(FFLAGS) $< -o $@ |
| .s | .o | $(AS) -c $(ASFLAGS) $< -o $@ |
| .y | .c | $(YACC) $(YFLAGS) $< |
| .l | .c | $(LEX) $(LFLAGS) $< |
个人经验:我在嵌入式项目里经常用隐式规则。比如编译STM32的固件,只要把CFLAGS设好,Make自动帮我处理所有.c文件。省了我不少事。
但要注意,隐式规则依赖变量。比如CC默认是cc,CFLAGS默认是空。你最好显式定义它们:
CC = arm-none-eabi-gcc
CFLAGS = -mcpu=cortex-m4 -mthumb -O2
4.3 模式规则:%通配符的威力
隐式规则虽然方便,但有时候不够灵活。比如我想把.c文件编译到另一个目录?或者我想自定义编译参数?这时候就需要模式规则了。
模式规则的核心就是 % 通配符。它匹配任意字符串,有点像shell里的 *。
看个例子:
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
这条规则的意思是:任何.o文件,都由同名的.c文件编译而来。$< 代表第一个依赖(就是.c文件),$@ 代表目标(.o文件)。
我曾经踩过的坑:%只能匹配文件名,不能匹配路径。比如你想把src/下的.c编译到build/下,直接写 build/%.o: src/%.c 是不行的。你得用 vpath 或者目录前缀技巧。
正确的做法是这样:
build/%.o: src/%.c
mkdir -p $(dir $@)
$(CC) -c $(CFLAGS) $< -o $@
这里 $(dir $@) 提取目标文件的目录部分,确保目录存在。
4.4 模式规则的高级用法
模式规则不止能处理一对一的转换。你想想看,一个.o文件依赖多个源文件?没问题:
%.o: %.c %.h
$(CC) -c $(CFLAGS) $< -o $@
这样只要.c或.h变了,对应的.o就会重新编译。我在做Linux驱动开发时经常这么用——头文件改了,所有依赖它的源文件都得重新编译。
还有一种情况:多个模式规则匹配同一个目标。Make会选哪个?答案是“最短路径”原则。它会选择依赖文件存在的那条规则。
记住这个原则:Make会尝试所有匹配的模式规则,找到第一个能成功构建的。所以你可以定义多个模式规则,让Make自己选。
4.5 隐式规则 vs 模式规则:怎么选?
我个人的习惯是这样的:
- 简单项目:直接用隐式规则,省事。设好CFLAGS就行。
- 中等项目:用模式规则覆盖隐式规则,方便加自定义逻辑。
- 大型项目:完全禁用隐式规则(
MAKEFLAGS += -r),全部用模式规则手动控制。
为什么要禁用隐式规则?因为大型项目里,隐式规则可能带来意外的匹配。比如你有个.f90文件,Make可能用Fortran规则去处理,但你其实想用C++编译器。这种问题排查起来很头疼。
4.6 实战:一个完整的模式规则示例
来,我写个实际项目里用过的Makefile片段:
SRC_DIR = src
BUILD_DIR = build
SOURCES = $(wildcard $(SRC_DIR)/*.c)
OBJECTS = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SOURCES))
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Iinclude $< -o $@
app: $(OBJECTS)
$(CC) $^ -o $@
.PHONY: clean
clean:
rm -rf $(BUILD_DIR) app
这段代码做了几件事:
- 用
wildcard收集所有.c文件 - 用
patsubst把路径从src/映射到build/ - 模式规则负责编译,自动创建build目录
- 最终链接成可执行文件
小技巧:@ 前缀让Make不打印命令本身,只打印输出。我习惯在编译命令前加 @,这样终端看起来清爽很多。
4.7 知识体系总览
下面这张图帮你理清本章的核心逻辑:
嗯,这张图把本章的核心脉络都串起来了。左边是隐式规则,右边是模式规则,底部是选择策略。你写Makefile的时候,对着这张图想,思路会清晰很多。
最后说一句:模式规则是Makefile里最强大的特性之一。我见过不少项目,几十个源文件,Makefile就十几行。靠的就是模式规则。你掌握了它,写Makefile的效率能翻好几倍。
可可精品资料库