7、Makefile隐含规则:隐含规则列表、模式规则(%.o: %.c)、VPATH与vpath
各位同学,今天我们来聊聊Makefile里一个特别有意思的话题——隐含规则。说实话,我刚开始学Makefile那会儿,看到别人写的Makefile就那么几行,却能编译整个项目,心里特别羡慕。后来才发现,原来人家用了隐含规则这个“黑魔法”。
隐含规则是什么?说白了就是Make自带的一套“潜规则”。你不需要明确告诉它怎么编译,它自己就知道该怎么做。比如你写个make test.o,它自动去找test.c,然后用gcc -c编译。是不是很神奇?
7.1 隐含规则列表:Make自带的“潜规则”
Make内置了很多隐含规则,我挑几个最常用的给大家说说。这些规则就像工具箱里的常用工具,你得知道它们在哪。
| 规则类型 | 命令 | 说明 |
|---|---|---|
| C编译 | $(CC) -c $(CFLAGS) $< -o $@ |
从.c生成.o |
| C++编译 | $(CXX) -c $(CXXFLAGS) $< -o $@ |
从.cpp或.cc生成.o |
| 链接 | $(CC) $(LDFLAGS) $^ -o $@ |
从.o生成可执行文件 |
| 汇编 | $(AS) $(ASFLAGS) $< -o $@ |
从.s生成.o |
| Yacc | $(YACC) $(YFLAGS) $< |
从.y生成.c |
| Lex | $(LEX) $(LFLAGS) $< |
从.l生成.c |
嗯,这里要注意一点:隐含规则使用的变量(比如CC、CFLAGS)都有默认值。CC默认是cc,CFLAGS默认是空的。我在项目中遇到过好几次,明明写了CFLAGS = -Wall -O2,结果编译时没生效——后来才发现变量名拼错了,写成CFLAG少了个S。
核心要点:隐含规则依赖变量名。变量名写对了,规则自动生效。变量名写错了,Make就傻眼了。
7.2 模式规则:%.o: %.c 的魔力
隐含规则虽然方便,但有时候不够灵活。比如我想让所有.c文件都用-mthumb -mcpu=cortex-m4编译,怎么办?这时候模式规则就派上用场了。
模式规则长这样:
%.o: %.c
$(CC) -c $(CFLAGS) -mthumb -mcpu=cortex-m4 $< -o $@
这个%是通配符,匹配文件名中相同的部分。比如main.o: main.c,%匹配的就是main。你想想看,这比一条条写规则省了多少事?
我个人习惯把模式规则和变量结合起来用:
CFLAGS = -Wall -O2 -mthumb -mcpu=cortex-m4
LDFLAGS = -T link.ld -nostartfiles
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
%.o: %.s
$(AS) -c $(ASFLAGS) $< -o $@
output.elf: main.o gpio.o uart.o
$(CC) $(LDFLAGS) $^ -o $@
这样写,编译选项统一管理,想改架构或者优化级别,改一个变量就行。我曾经在一个项目里接手别人的Makefile,里面硬编码了十几条编译规则,每条都不一样。改个编译选项得改半天,那叫一个痛苦。
小技巧:模式规则可以叠加。如果你写了自定义模式规则,它会覆盖Make内置的隐含规则。所以不用担心冲突,Make会优先用你的规则。
7.3 VPATH与vpath:让Make学会“找文件”
好了,现在有个新问题。如果你的源文件不在当前目录,比如在src/子目录里,Make能找到吗?默认情况下,不能。这时候就需要VPATH和vpath出场了。
VPATH是个变量,告诉Make去哪些目录找源文件:
VPATH = src:lib
main.o: main.c
$(CC) -c $(CFLAGS) $< -o $@
这样,Make会先在当前目录找main.c,找不到就去src/找,再找不到就去lib/找。目录之间用冒号分隔。
vpath指令更精细,可以指定某种后缀的文件去哪找:
vpath %.c src
vpath %.h include
vpath %.o build
这意思是:.c文件去src/找,.h文件去include/找,.o文件去build/找。是不是很清晰?
我建议在实际项目中,用vpath而不是VPATH。为什么?因为VPATH太“粗放”了,所有文件都去那几个目录找,万一有同名文件就麻烦了。vpath可以精确控制,避免混乱。
避坑指南:我曾经在项目里用VPATH = src,结果src/目录下有个main.c,当前目录也有个main.c。Make优先用了当前目录的,但当前目录那个是旧版本。排查了半天才发现是文件优先级的问题。所以,用vpath,别用VPATH。
7.4 实战:一个完整的嵌入式项目Makefile
说了这么多,咱们来写个完整的例子。假设项目结构是这样的:
project/
├── src/
│ ├── main.c
│ ├── gpio.c
│ └── uart.c
├── include/
│ ├── gpio.h
│ └── uart.h
├── build/
└── Makefile
Makefile可以这样写:
CC = arm-none-eabi-gcc
CFLAGS = -Wall -O2 -mthumb -mcpu=cortex-m4 -Iinclude
LDFLAGS = -T link.ld -nostartfiles
# 告诉Make去哪找源文件和头文件
vpath %.c src
vpath %.h include
# 目标文件列表
OBJS = main.o gpio.o uart.o
# 默认目标
all: firmware.elf
# 模式规则:编译
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
# 链接
firmware.elf: $(OBJS)
$(CC) $(LDFLAGS) $^ -o $@
# 清理
clean:
rm -f *.o firmware.elf
.PHONY: all clean
你看,这个Makefile只有十几行,但能处理完整的编译流程。源文件在src/,头文件在include/,编译出来的.o文件和.elf文件都在当前目录(你也可以改到build/)。
运行make,Make会自动去src/找.c文件,去include/找.h文件。这就是隐含规则+模式规则+vpath的威力。
7.5 知识体系图
下面这张图总结了本章的核心逻辑,我画的时候特意把各个概念的关系理清楚了:
这张图把隐含规则、模式规则和VPATH/vpath的关系理得很清楚。三者各司其职,但最终目标是一样的——让Makefile更简洁、更智能。
好了,关于隐含规则就讲这么多。记住一句话:隐含规则是Make的“默认行为”,模式规则是“自定义行为”,VPATH/vpath是“找文件的行为”。把这三样用好,你的Makefile就能从“能跑”变成“优雅”。
本章总结:
- 隐含规则:Make内置的编译规则,依赖变量名(CC、CFLAGS等)
- 模式规则:用
%通配符自定义规则,覆盖隐含规则 - VPATH:全局搜索路径,简单但粗放
- vpath:按文件后缀精确搜索,推荐使用
公众号:蓝海资料掘金营,微信deep3321