4、模式规则与静态模式:%通配符的使用,静态模式规则让代码更简洁

4.1 从重复劳动中解放出来

说实话,我刚开始写 Makefile 的时候,干过不少蠢事。

比如一个项目里有十几个 .c 文件,我就老老实实写了十几条规则:

foo.o: foo.c
    gcc -c foo.c -o foo.o

bar.o: bar.c
    gcc -c bar.c -o bar.o

baz.o: baz.c
    gcc -c baz.c -o baz.o

你想想看,这多累啊。而且一旦要加一个新文件,又得复制粘贴改一遍。我当时就在想,Make 能不能聪明一点,自动匹配这些文件?

答案是:能。用 % 通配符。

4.2 % 通配符:模式规则的核心

% 在 Makefile 里,相当于 shell 里的 *。它匹配任意长度的字符串。比如 %.o 匹配所有以 .o 结尾的文件名。

用模式规则,上面的代码可以简写成一行:

%.o: %.c
    gcc -c $< -o $@

这里 $< 代表第一个依赖(也就是 .c 文件),$@ 代表目标(.o 文件)。

嗯,是不是清爽多了?

核心理解: 模式规则的本质,就是让 Make 自动推导出目标与依赖之间的对应关系。你只需要写一次规则,所有符合模式的文件都能套用。

4.3 静态模式规则:更精准的控制

模式规则虽然好用,但它有个问题:它会匹配所有符合模式的目标。有时候你只想对特定的文件列表应用规则,而不是全部。

这时候就该静态模式规则出场了。

语法是这样的:

目标列表 : 目标模式 : 依赖模式
    命令

举个例子:

OBJS = foo.o bar.o baz.o

$(OBJS): %.o: %.c
    gcc -c $< -o $@

这条规则的意思是:对于 OBJS 列表里的每一个 .o 文件,它依赖同名的 .c 文件,然后用 gcc 编译。

我在项目中遇到过这样的情况:项目里既有 .c 文件,也有 .cpp 文件。如果用普通的模式规则,可能会误伤。用静态模式规则,我可以精确控制哪些文件走 C 编译器,哪些走 C++ 编译器:

C_OBJS = foo.o bar.o
CPP_OBJS = main.o utils.o

$(C_OBJS): %.o: %.c
    gcc -c $< -o $@

$(CPP_OBJS): %.o: %.cpp
    g++ -c $< -o $@

你看,这样就不会搞混了。

4.4 模式规则 vs 静态模式规则:一张图看懂

我画了张图,帮你理清两者的区别:

模式规则 vs 静态模式规则 模式规则 %.o: %.c • 匹配所有符合模式的目标 • 自动推导,无需指定列表 • 适合:全项目统一编译 示例: %.o: %.c gcc -c $< -o $@ → 所有 .o 都走这条规则 静态模式规则 $(OBJS): %.o: %.c • 只匹配指定列表中的目标 • 需要显式声明目标列表 • 适合:混合编译、精确控制 示例: OBJS = foo.o bar.o $(OBJS): %.o: %.c gcc -c $< -o $@ → 只有 foo.o bar.o 走这条规则 选择建议:全项目统一用模式规则,混合编译用静态模式规则

4.5 实战:一个完整的例子

光说不练假把式。我们来看一个实际项目中的 Makefile:

# 源文件列表
SRCS = main.c utils.c network.c
OBJS = $(SRCS:.c=.o)
TARGET = myapp

# 默认目标
all: $(TARGET)

# 静态模式规则:编译所有 .o 文件
$(OBJS): %.o: %.c
    gcc -Wall -O2 -c $< -o $@

# 链接
$(TARGET): $(OBJS)
    gcc $^ -o $@

# 清理
clean:
    rm -f $(OBJS) $(TARGET)

.PHONY: all clean

这里有个小技巧:$(SRCS:.c=.o) 是 Make 的替换引用语法,它把 SRCS 里所有 .c 后缀换成 .o。这样你只需要维护源文件列表,目标文件列表自动生成。

我的习惯: 我一般会把源文件列表放在 Makefile 顶部,用 SRCS 变量管理。这样后续加文件、删文件,只需要改这一处。配合静态模式规则,整个编译流程自动适配,省心不少。

4.6 避坑指南

我曾经踩过的坑:

  • % 匹配的是整个路径。比如 src/%.o: src/%.c,这里的 % 匹配的是 src/ 之后的部分。如果你目标文件在 build/ 目录,依赖在 src/ 目录,模式规则就不好使了。这时候需要配合 vpath 或目录变量来处理。
  • 静态模式规则的目标列表不能为空。如果 OBJS 是空的,Make 会报错。我一般会在列表前加个检查,或者确保至少有一个源文件。
  • 模式规则不能有多个目标模式。比如 %.o %.d: %.c 这种写法是不合法的。如果你需要同时生成 .o 和 .d 文件,得用其他技巧。

4.7 进阶:多目录下的静态模式

项目大了,源文件不可能全放根目录。我习惯把源文件按模块分目录存放:

SRCS = src/main.c \
       src/utils/helper.c \
       src/network/tcp.c

OBJS = $(SRCS:.c=.o)

# 注意:这里的目标模式要包含路径
$(OBJS): %.o: %.c
    gcc -Wall -O2 -c $< -o $@

你看,%.o 匹配的是 src/main.o、src/utils/helper.o 这样的完整路径。依赖模式 %.c 也对应匹配。Make 会自动把 % 部分替换成对应的路径前缀。

说白了,% 就是个占位符。它匹配什么,就替换成什么。这个机制非常灵活,但也容易让人迷惑。记住一点:目标里的 % 和依赖里的 % 是同一个值。

4.8 小结

模式规则和静态模式规则,是 Makefile 从「能用」到「好用」的关键一步。它们帮你摆脱重复劳动,让编译规则变得简洁、可维护。

我个人建议:

  • 小项目、文件类型单一:直接用模式规则 %.o: %.c
  • 大项目、混合编译:用静态模式规则,精确控制每个文件列表
  • 多目录项目:注意路径匹配,% 会包含路径前缀

嗯,掌握了这些,你的 Makefile 已经比大多数人的要专业了。

可可精品资料库