Makefile隐含规则
📝 摘要
在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译C/C++的源程序为中间目标文件(Unix下是.o文件,Windows下是.obj文件)。
本章讲述的就是一些在Makefile中的"隐含的",早先约定了的,不需要我们再写出来的规则。
隐含规则简介
什么是隐含规则
"隐含规则"也就是一种惯例,make会按照这种"惯例"心照不宣地来运行,那怕我们的Makefile中没有书写这样的规则。
| 特性 | 说明 | 示例 |
|---|---|---|
| 自动推导 | make会自动推导出规则 | .c → .o 文件编译 |
| 系统变量 | 使用预定义的系统变量 | CFLAGS 控制编译参数 |
| 约定俗成 | 遵循通用的构建惯例 | C编译器使用 cc 命令 |
隐含规则的优势
- ✅ 简化Makefile:减少重复的规则编写
- ✅ 标准化构建:遵循通用的构建约定
- ✅ 灵活配置:通过变量定制构建参数
- ✅ 兼容性好:支持多种编程语言和工具
使用隐含规则
基本使用方法
如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。
基础示例
foo : foo.o bar.o
cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
📊 示例分析
| 文件 | 状态 | 说明 |
|---|---|---|
| foo.o | 未定义规则 | make会自动推导生成规则 |
| bar.o | 未定义规则 | make会自动推导生成规则 |
| foo.c | 源文件 | 自动作为foo.o的依赖 |
| bar.c | 源文件 | 自动作为bar.o的依赖 |
隐含规则的工作原理
- 规则查找:make在"隐含规则库"中寻找可用规则
- 自动推导:将
.o目标的依赖文件设为.c - 命令生成:使用
cc -c $(CFLAGS) foo.c生成目标
等价的显式规则
# 以下规则会被隐含规则自动生成,无需手写
foo.o : foo.c
cc -c foo.c $(CFLAGS)
bar.o : bar.c
cc -c bar.c $(CFLAGS)
规则优先级
💡 优先级机制
在make的"隐含规则库"中,每一条隐含规则都有其顺序,越靠前的则是越被经常使用的。
优先级示例
foo.o : foo.p # Pascal源文件依赖
结果分析:
- 如果目录下同时存在
foo.c和foo.p - C语言规则优先级高于Pascal规则
- make会选择C语言规则生成
foo.o
禁用隐含规则推导
如果你确实不希望任何隐含规则推导,那么:
# 错误做法:只写依赖规则,不写命令
foo.o : foo.p
# 正确做法:写完整的规则
foo.o : foo.p
$(PC) -c $(PFLAGS) foo.p
隐含规则控制
控制参数
| 参数 | 功能 |
|---|---|
| -r 或 --no-builtin-rules | 取消所有预设置的隐含规则 |
| -R 或 --no-builtin-variables | 取消预定义变量对隐含规则的作用 |
默认后缀列表
.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S,
.mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo,
.w, .ch, .web, .sh, .elc, .el
常用隐含规则介绍
1️⃣ C程序编译规则
📝 规则描述
- 目标模式:
<n>.o - 依赖推导:
<n>.c - 生成命令:
$(CC) -c $(CPPFLAGS) $(CFLAGS)
应用示例
# 自动规则:main.o 依赖于 main.c
main.o: main.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) main.c
2️⃣ C++程序编译规则
📝 规则描述
- 目标模式:
<n>.o - 依赖推导:
<n>.cc或<n>.cpp - 生成命令:
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
3️⃣汇编和汇编预处理规则
📝 规则描述
| 目标 | 依赖 | 命令 | 说明 |
|---|---|---|---|
<n>.o |
<n>.s |
$(AS) $(ASFLAGS) |
汇编编译 |
<n>.s |
<n>.S |
$(CPP) $(CPPFLAGS) |
汇编预处理 |
4️⃣ 链接Object文件规则
📝 规则描述
- 目标模式:
<n> - 依赖推导:
<n>.o - 生成命令:
$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)
复杂示例
x : y.o z.o
执行过程:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o
⚠️ 注意事项
如果没有一个源文件和你的目标名字相关联,那么你最好写出自己的生成规则,不然隐含规则会报错。
5️⃣ Lint库创建规则
📝 规则描述
- 目标模式:
<n>.ln - 依赖推导:
<n>.c、<n>.y、<n>.l - 生成命令:
$(LINT) $(LINTFLAGS) $(CPPFLAGS) -i
隐含规则使用的变量
变量分类
隐含规则中使用的变量分为两种:
| 类型 | 说明 | 示例 |
|---|---|---|
| 命令相关 | 指定使用的工具程序 | CC、CXX、AS |
| 参数相关 | 控制命令的参数 | CFLAGS、CXXFLAGS |
命令相关变量
| 变量 | 说明 | 默认值 |
|---|---|---|
| AR | 函数库打包程序 | ar |
| AS | 汇编语言编译程序 | as |
| CC | C语言编译程序 | cc |
| CXX | C++语言编译程序 | g++ |
| CO | 从RCS文件中扩展文件程序 | co |
| CPP | C程序的预处理器 | $(CC) -E |
| FC | Fortran和Ratfor编译器 | f77 |
| GET | 从SCCS文件中扩展文件的程序 | get |
| LEX | Lex方法分析器程序 | lex |
| PC | Pascal语言编译程序 | pc |
| YACC | Yacc文法分析器(C程序) | yacc |
| YACCR | Yacc文法分析器(Ratfor程序) | yacc -r |
| MAKEINFO | 转换Texinfo源文件到Info文件 | makeinfo |
| TEX | 从TeX源文件创建DVI文件 | tex |
| TEXI2DVI | 从Texinfo源文件创建DVI文件 | texi2dvi |
| WEAVE | 转换Web到TeX的程序 | weave |
| CWEAVE | 转换C Web到TeX的程序 | cweave |
| TANGLE | 转换Web到Pascal语言 | tangle |
| CTANGLE | 转换C Web到C | ctangle |
| RM | 删除文件命令 | rm -f |
参数相关变量
| 变量 | 说明 | 默认值 |
|---|---|---|
| ARFLAGS | 函数库打包程序AR命令的参数 | rv |
| ASFLAGS | 汇编语言编译器参数 | 空 |
| CFLAGS | C语言编译器参数 | 空 |
| CXXFLAGS | C++语言编译器参数 | 空 |
| COFLAGS | RCS命令参数 | 空 |
| CPPFLAGS | C预处理器参数 | 空 |
| FFLAGS | Fortran语言编译器参数 | 空 |
| GFLAGS | SCCS "get"程序参数 | 空 |
| LDFLAGS | 链接器参数 | 空 |
| LFLAGS | Lex文法分析器参数 | 空 |
| PFLAGS | Pascal语言编译器参数 | 空 |
| RFLAGS | Ratfor程序的Fortran编译器参数 | 空 |
| YFLAGS | Yacc文法分析器参数 | 空 |
变量定制示例
# 定制编译器和参数
CC = gcc
CFLAGS = -g -Wall -O2
CPPFLAGS = -DDEBUG
# 隐含规则会使用这些变量
# 等价于:gcc -c -g -Wall -O2 -DDEBUG main.c
main.o: main.c
隐含规则链
有些时候,一个目标可能被一系列的隐含规则所作用。我们把这一系列的隐含规则叫做"隐含规则链"。
规则链示例
场景:.y → .c → .o
foo.y → foo.c → foo.o
执行过程:
- 第一步:Yacc规则将
foo.y转换为foo.c - 第二步:C编译规则将
foo.c编译为foo.o
中间目标
📝 定义
在规则链中产生的临时文件称为"中间目标"(如上例中的foo.c)。
中间目标特性
| 特性 | 说明 |
|---|---|
| 条件触发 | 除非中间目标不存在,才会引发中间规则 |
| 自动清理 | 目标成功产生后,中间目标文件会被rm -f删除 |
中间目标控制
强制声明中间目标
.INTERMEDIATE : mid
阻止自动删除
.SECONDARY : sec
保护特定模式
.PRECIOUS : %.o
规则链限制
🚨 防止无限递归
在"隐含规则链"中,禁止同一个目标出现两次或两次以上,这样可防止make自动推导时出现无限递归。
优化机制
Make会优化一些特殊的隐含规则,而不生成中间文件:
# 优化前:foo.c → foo.o → foo
cc -c foo.c -o foo.o
cc foo.o -o foo
rm foo.o
# 优化后:foo.c → foo(直接)
cc -o foo foo.c
模式规则
模式规则概述
你可以使用模式规则来定义一个隐含规则。模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有%字符。
模式规则语法
基本语法
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
语法要素
| 元素 | 说明 |
|---|---|
| % | 表示一个或多个任意字符 |
| 目标模式 | 至少包含一个% |
| 依赖模式 | %的取值由目标决定 |
模式匹配规则
匹配示例
| 模式 | 匹配文件 | 最小长度 |
|---|---|---|
| %.c | test.c |
3个字符 |
| s.%.c | s.main.c |
5个字符 |
| % | 任意文件 | 1个字符 |
模式传递示例
%.o : %.c ; <command>
说明:
- 如果目标是
a.o b.o - 那么依赖就是
a.c b.c
高级模式规则
多目标模式
%.tab.c %.tab.h: %.y
bison -d $<
功能:
- 将所有
.y文件转换为对应的.tab.c和.tab.h文件 - 一个规则生成两个目标
复杂依赖关系
# 假设的依赖关系
foo: parse.tab.o scan.o
scan.o: parse.tab.h
parse.tab.o parse.tab.h: parse.y
执行流程:
parse.y更新- 执行
bison -d parse.y - 生成
parse.tab.c和parse.tab.h - 重新编译相关的
.o文件
自动化变量
自动化变量的作用
在模式规则中,目标和依赖文件都是一系列的文件,自动化变量可以自动地挨个取出这些文件,直至所有符合模式的文件都取完。
⚠️ 使用范围
自动化变量只应出现在规则的命令中。
自动化变量列表
| 变量 | 说明 | 示例 |
|---|---|---|
| $@ | 规则中的目标文件集 | 在模式规则中表示当前目标 |
| $% | 函数库文件中的目标成员名 | foo.a(bar.o)中的bar.o |
| $< | 依赖目标中的第一个目标名字 | 第一个依赖文件 |
| $? | 所有比目标新的依赖目标集合 | 需要更新的依赖文件 |
| $^ | 所有依赖目标的集合(去重) | 所有依赖文件,去除重复 |
| $+ | 所有依赖目标的集合(保留重复) | 所有依赖文件,保留重复 |
| $* | 目标模式中%及其之前的部分 |
模式匹配的"茎" |
自动化变量示例
基础使用
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
函数库更新
lib : foo.o bar.o lose.o win.o
ar r lib $?
说明: $?只包含比lib新的.o文件,提高效率。
目录和文件名变量
变量扩展
| 基础变量 | 目录部分 | 文件部分 |
|---|---|---|
| $@ | $(@D) |
$(@F) |
| $* | $(*D) |
$(*F) |
| $% | $(%D) |
$(%F) |
| $< | $(<D) |
$(<F) |
| $^ | $(^D) |
$(^F) |
| $+ | $(+D) |
$(+F) |
| $? | $(?D) |
$(?F) |
目录文件分离示例
# 假设 $@ = dir/foo.o
$(@D) # 结果:dir
$(@F) # 结果:foo.o
# 假设 $@ 中没有斜杠
$(@D) # 结果:.(当前目录)
适用范围
📝 重要提醒
这些变量只使用在规则的命令中,而且一般都是"显式规则"和"静态模式规则"。其在隐含规则中并没有意义。
模式的匹配
模式匹配机制
"茎"的概念
在模式匹配中,我们把%所匹配的内容叫做"茎"。
| 模式 | 文件 | 茎 |
|---|---|---|
| %.c | test.c |
test |
| s.%.c | s.main.c |
main |
| lib%.a | libmath.a |
math |
茎的传递
基本传递
%.o : %.c
$(CC) -c $< -o $@
说明: 依赖目标的"茎"会传给目标,作为目标中的"茎"。
重载内建隐含规则
你可以重载内建的隐含规则,或定义全新的隐含规则。
重载方法
重新定义命令
%.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date) $< -o $@
取消内建规则
%.o : %.s
# 没有命令,取消内建的汇编规则
定义新规则
%.pdf : %.tex
pdflatex $<
规则优先级
| 位置 | 优先级 | 说明 |
|---|---|---|
| 靠前 | 高 | 先定义的规则优先级高 |
| 靠后 | 低 | 后定义的规则优先级低 |
隐含规则搜索算法
算法概述
当make需要为目标T寻找隐含规则时,会执行以下搜索算法。
搜索步骤
第1步:目录分离
目标:src/foo.o
分离结果:
- D = src/
- N = foo.o
第2步:创建模式规则列表
- 如果目标模式包含斜杠,则匹配T
- 否则匹配N
第3步:过滤通用模式
如果列表中有匹配所有文件的模式(如%),移除其它模式。
第4步:移除无命令规则
移除列表中没有命令的规则。
第5步:直接匹配搜索
对于列表中的每一个模式规则:
- 推导茎:S应该是T或N匹配于模式中
%的非空部分 - 计算依赖:将依赖文件中的
%替换成茎S - 添加目录:如果目标模式中没有斜杠,将D加到每个依赖文件开头
- 测试存在性:检查所有依赖文件是否存在或"理当存在"
- 采用规则:如果条件满足,采用此规则并退出
第6步:递归搜索
如果第5步没有找到规则,进行更深入的搜索:
- 忽略终止规则:跳过终止规则
- 计算依赖:同第5步
- 测试存在性:检查依赖文件
- 递归查找:对不存在的依赖文件递归调用算法
- 采用规则:如果所有依赖都能解决,采用此规则
第7步:默认规则
如果没有隐含规则可用,查看.DEFAULT规则:
.DEFAULT:
@echo "Don't know how to make $@"
搜索流程图
目标T
↓
分离目录 (D, N)
↓
创建模式规则列表
↓
过滤和清理规则
↓
直接匹配搜索 ──→ 找到规则 ──→ 执行命令
↓
递归搜索 ──→ 找到规则 ──→ 执行命令
↓
.DEFAULT规则 ──→ 执行默认命令
↓
报错:无法构建目标
"理当存在"的定义
文件"理当存在"的条件:
| 条件 | 说明 |
|---|---|
| 显式目标 | 被定义为另一个规则的目标文件 |
| 显式依赖 | 是一个显式规则的依赖文件 |
| 实际存在 | 文件在文件系统中存在 |
算法执行时机
📝 重要说明
一旦规则被找到,就会执行其相应的命令,而此时,我们的自动化变量的值才会生成。
📋 总结
- ✅ 隐含规则的基本概念和工作原理
- ✅ 常用隐含规则的详细说明和应用
- ✅ 隐含规则使用的变量系统
- ✅ 隐含规则链的概念和中间目标管理
- ✅ 模式规则的定义和使用方法
- ✅ 自动化变量的完整用法
- ✅ 模式匹配和茎传递机制
- ✅ 内建规则的重载方法
- ✅ 隐含规则搜索算法的详细流程

浙公网安备 33010602011771号