隐式规则能自动编译.c→.o,因gnu make启动时加载内置规则库,含%.o:%.c配方及默认变量(如cc=cc),只要未显式定义且满足文件名匹配,即自动调用$(cc)-c执行。

隐式规则为什么能自动编译 .c → .o
GNU Make 内置了对 .c 文件到 .o 文件的隐式规则,只要目标是 %.o、依赖是 %.c,且你没显式定义该规则,make 就会自动调用 $(CC) -c 编译。它不是“猜”,而是查表:Make 在启动时加载了一套标准规则库,其中就包含 .c → .o 的配方和默认变量(如 CC = cc、CFLAGS = 空)。
你不需要写 main.o: main.c 这类重复规则,只要确保:
-
main.o出现在某个目标的依赖列表里(比如program: main.o func.o) - 当前目录下存在
main.c - 没用空格替代 Tab 写出冲突的显式规则
怎么让隐式规则生效又不被覆盖
最常见失效原因是:你写了 main.o: main.c func.h 这种显式规则,但只列了部分头文件,或者漏了 func.h 的修改触发——这时隐式规则就被屏蔽了,而你的显式规则又没覆盖全部依赖,导致头文件改了却不重编。
正确做法是用模式规则 + 自动依赖生成,而不是手写每个 .o 规则:
- 删掉所有形如
main.o: main.c xxx.h的手动规则 - 只保留一条通用模式规则:
%.o: %.c,后面跟一个 Tab 开头的命令行 - 命令里用
$(CC) $(CFLAGS) -c $,其中 <code>$ 是第一个依赖(<code>.c),$@是目标(.o) - 配合
-MMD -MP让编译器自动生成.d依赖文件,并用-include加载它们
示例片段:
CC = gcc CFLAGS = -Wall -std=c99 -MMD -MP program: main.o func.o $(CC) $^ -o $@ %.o: %.c $(CC) $(CFLAGS) -c $ <h3>为什么 <code>%.o: %.c</code> 比手写规则更可靠</h3> <p>手写规则容易漏头文件依赖,而 <code>%.o: %.c</code> 是“占位符”,它本身不指定具体头文件;真正决定是否重编的是编译器生成的 <code>.d</code> 文件(比如 <code>main.d</code> 里会列出 <code>main.o: main.c func.h utils.h</code>)。这样,哪怕你新增了 <code>#include "log.h"</code>,下次 <code>make</code> 也会自动读取更新后的 <code>main.d</code> 并判断需重编 <code>main.o</code>。</p> <p>几个关键点:</p>
-
-MMD生成仅含非系统头的依赖(跳过/usr/include下的) -
-MP为每个依赖项加一个空目标,避免头文件被删后 make 报错 -
-include *.d是“软包含”:文件不存在也不报错,适合首次构建 - 不要用
$(wildcard *.d)替代-include *.d,前者在首次构建时展开为空,导致依赖丢失
常见踩坑:隐式规则被悄悄禁用
以下情况会让 GNU Make 放弃使用内置隐式规则:
- 目标名不含标准后缀(比如你写
main.obj而不是main.o) - 写了同名的空规则(如
main.o:后面没命令也没依赖) - 用了
.SUFFIXES:清空后缀列表,或重新定义了.SUFFIXES: .c .o但顺序/内容不对 - 在命令行传入
--no-builtin-rules或设置了MAKEFLAGS += --no-builtin-rules
检查是否启用隐式规则,运行:make -p | grep -A5 '\.c\.o'。如果看不到 .c .o: 对应的规则块,说明它已被抑制——优先查 .SUFFIXES 和空规则。
多文件项目里,隐式规则的价值不在“少写几行”,而在把依赖管理交给编译器和 make 的协作机制。手写规则看似可控,实则把头文件依赖变成维护黑洞;而 %.o: %.c 搭配 -MMD,才是真正贴合 C 语言预处理本质的做法。











