4. 变量与赋值

上一章我们还在手动为每个 .c 文件写编译命令,对吧?说实话,那会儿我看着满屏重复的规则,心里就一个念头——这跟复制粘贴有啥区别?

这一章,我们来解决这个问题。核心就两个东西:自动变量 和 模式规则。搞懂了它们,你的 Makefile 就能从“写死”变成“通用”。

4.1 自动变量:Makefile 里的“代词”

先想想看,我们写规则时,最烦的是什么?是重复写目标文件名、依赖文件名。自动变量就是干这个的——它们像代词一样,自动指代当前规则中的特定部分。

我列几个最常用的,你记一下:

自动变量 含义 举个例子(规则 target: a.o b.o)
$@ 当前规则的目标文件 就是 target
$< 第一个依赖文件 就是 a.o
$^ 所有依赖文件(去重) 就是 a.o b.o
$? 所有比目标新的依赖文件 如果 b.o 比 target 新,就是 b.o
$* 模式匹配中的“茎”(stem) 后面讲模式规则时会用到
我的习惯: 刚开始用自动变量时,我总记不住 $< 和 $^ 的区别。后来我给自己编了个口诀——“< 是第一个,^ 是全部”。你试试看,是不是好记多了?

来看个实际例子。以前我们写编译规则是这样的:

main.o: main.c
    gcc -c main.c -o main.o

utils.o: utils.c
    gcc -c utils.c -o utils.o

用了自动变量后,变成这样:

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

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

你看,命令部分完全一样了。$< 自动变成 main.c 或 utils.c,$@ 自动变成 main.o 或 utils.o。这就是“通用化”的第一步。

4.2 模式规则:用 % 匹配一类文件

自动变量解决了命令部分的重复,但规则头(target: dependency)还是得一个个写。模式规则就是来解决这个的。

模式规则的核心是 % 通配符。它匹配文件名中的任意部分。比如 %.o: %.c 的意思是:所有 .o 文件都依赖于同名的 .c 文件。

有了模式规则,上面的例子可以写成一行:

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

就这么一行,就能匹配项目中所有的 .c 文件。Make 会自动找到每个 .c 文件,生成对应的 .o 文件。

我曾经踩过的坑: 模式规则中的 % 只能匹配文件名的一部分,不能匹配路径。比如 %.o: %.c 能匹配 src/main.o 和 src/main.c 吗?不能!因为 % 匹配的是 src/main,但 Make 会把路径也当作匹配的一部分。如果你有子目录,需要用 src/%.o: src/%.c 这种形式。

4.3 实战:用自动变量 + 模式规则重写编译规则

好,理论讲完了,我们直接上实战。假设项目结构是这样的:

project/
├── main.c
├── utils.c
├── data.c
└── Makefile

用我们刚学的知识,写一个通用的 Makefile:

CC = gcc
CFLAGS = -Wall -g

# 目标文件列表
OBJS = main.o utils.o data.o

# 最终目标
app: $(OBJS)
    $(CC) $^ -o $@

# 模式规则:所有 .o 都从 .c 编译而来
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

# 清理
clean:
    rm -f *.o app

这个 Makefile 已经相当通用了。你只需要修改 OBJS 列表,就能适配不同的项目。

但说实话,每次有新文件都要手动加到 OBJS 里,还是有点烦。有没有更自动化的办法?

4.4 更进一步:自动获取源文件列表

我们可以用 Make 的内置函数来自动获取源文件列表。比如:

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

$(wildcard *.c) 会展开成当前目录下所有 .c 文件的列表。$(SRCS:.c=.o) 会把列表中的 .c 后缀替换成 .o。

这样,你新增一个 .c 文件,Makefile 完全不用改。是不是很爽?

完整的 Makefile 变成这样:

CC = gcc
CFLAGS = -Wall -g

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
TARGET = app

$(TARGET): $(OBJS)
    $(CC) $^ -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    rm -f $(OBJS) $(TARGET)
核心思路: 自动变量解决命令重复,模式规则解决规则重复,wildcard 函数解决文件列表重复。三者结合,你的 Makefile 就具备了初步的“工程化”能力。

4.5 知识体系:自动变量与模式规则的关系

为了让你更直观地理解它们之间的关系,我画了张图:

自动变量与模式规则知识体系 Makefile 规则 自动变量 模式规则 $@ $< $^ %.o: %.c $* wildcard 核心思想:用自动变量消除命令重复,用模式规则消除规则重复 最终目标:写一次规则,匹配所有文件

4.6 避坑指南:我踩过的几个坑

最后,分享几个我实际项目中踩过的坑,你遇到了能少走弯路:

  • 模式规则中的 % 不能匹配路径分隔符 —— 如果你有子目录,比如 src/main.c,用 %.o: %.c 是匹配不到的。需要用 src/%.o: src/%.c 或者用 vpath 指令。
  • $^ 会去重 —— 如果同一个文件被多次列出,$^ 只会保留一个。这通常是好事,但如果你需要保留重复项(比如链接时),要用 $+。
  • 自动变量只在规则的命令部分生效 —— 你不能在依赖列表里用 $@ 或 $<。比如 $@: $^.c 这种写法是错的。
  • 小心通配符展开时机 —— $(wildcard *.c) 是在 Makefile 解析时展开的。如果你在规则中动态生成 .c 文件,它可能不会包含新文件。这时候需要用 $(shell ...) 函数。
我的建议: 刚开始用模式规则时,先用一个简单的项目练手。把规则写好后,用 make -p 查看 Make 内部解析后的规则,看看它是不是按你预期的方式工作的。这招帮我发现过不少隐藏问题。