3、Makefile变量进阶:自动变量、预定义变量与模式规则
说实话,刚接触Makefile那会儿,我觉得变量就是用来偷懒的——少打几个字而已。后来在项目里被坑了几次,才明白变量这东西,用好了能让你少掉一半头发。
这一节我们聊点进阶的。自动变量、预定义变量、模式规则、静态模式规则。嗯,名字听着唬人,其实说白了就是Makefile里的一些「快捷方式」和「套路」。我一个个给你拆开讲。
3.1 自动变量:$@、$<、$^ 到底是个啥?
先看个例子。你写一个编译规则:
main.o: main.c
gcc -c main.c -o main.o
这代码没问题,但不够聪明。如果我把目标文件改成 app.o,或者源文件改成 app.c,你得手动改两处。自动变量就是来解决这个问题的。
我常用的三个自动变量:
| 变量 | 含义 | 例子 |
|---|---|---|
$@ |
目标文件名 | 在 main.o: main.c 中,$@ 就是 main.o |
$< |
第一个依赖文件 | 在 main.o: main.c 中,$< 就是 main.c |
$^ |
所有依赖文件(去重) | 在 main.o: main.c defs.h 中,$^ 就是 main.c defs.h |
用自动变量改写上面的规则:
main.o: main.c
gcc -c $< -o $@
你看,现在不管目标叫什么,源文件叫什么,我都不用改命令行了。我在项目里见过有人写了上百条类似的规则,每条都硬编码文件名。后来改成自动变量,代码量直接砍掉三分之二。
3.2 预定义变量:Makefile自带的「快捷键」
除了自动变量,Makefile还内置了一些预定义变量。这些变量你不用定义就能直接用。我挑几个最常用的:
| 变量 | 含义 | 默认值 |
|---|---|---|
CC |
C编译器 | cc |
CFLAGS |
C编译器选项 | 空 |
LDFLAGS |
链接器选项 | 空 |
RM |
删除命令 | rm -f |
举个例子:
CC = gcc
CFLAGS = -Wall -O2
main.o: main.c
$(CC) $(CFLAGS) -c $< -o $@
为什么要用预定义变量?说白了就是「约定大于配置」。别人看你的Makefile,一眼就知道编译器是 CC,选项是 CFLAGS。不用猜。
CC 但没给它赋值,结果系统默认用了 cc。而 cc 在我那台机器上是个软链接,指向了老版本的gcc。编译出来的程序跑起来各种诡异。从那以后,我每次写Makefile都会显式指定 CC 的值。
3.3 模式规则:一条规则搞定一类文件
假设你有三个源文件:main.c、utils.c、io.c。按之前的写法,你得写三条规则:
main.o: main.c
gcc -c main.c -o main.o
utils.o: utils.c
gcc -c utils.c -o utils.o
io.o: io.c
gcc -c io.c -o io.o
烦不烦?模式规则就是来救场的。它用 % 作为通配符,匹配任意文件名:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
这条规则的意思是:任何 .o 文件,都由同名的 .c 文件编译而来。一条规则,搞定所有源文件。
我刚开始用模式规则的时候,总觉得不踏实——万一匹配错了怎么办?后来发现,Makefile的匹配机制其实很严谨。它只会匹配那些确实存在的依赖文件。如果 foo.o 没有对应的 foo.c,这条规则就不会被触发。
3.4 静态模式规则:更精准的匹配
模式规则虽然好用,但它太「泛」了。有时候我只想对某几个文件应用规则,而不是所有文件。这时候就该静态模式规则上场了。
语法长这样:
目标列表 : 目标模式 : 依赖模式
命令
看个例子:
OBJS = main.o utils.o io.o
$(OBJS): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
这里 $(OBJS) 指定了目标列表,只有这三个文件会匹配这条规则。其他 .o 文件就算存在,也不会被这条规则处理。
SRC_DIR1_OBJS = dir1/a.o dir1/b.o
SRC_DIR2_OBJS = dir2/c.o dir2/d.o
$(SRC_DIR1_OBJS): %.o: %.c
$(CC) $(CFLAGS) -I dir1 -c $< -o $@
$(SRC_DIR2_OBJS): %.o: %.c
$(CC) $(CFLAGS) -I dir2 -c $< -o $@
这样每个目录的源文件用各自的编译选项,互不干扰。
3.5 知识体系一览
说了这么多,我画张图帮你理一理思路:
你看这张图,从左到右、从上到下,其实是一个从「通用」到「精准」的递进关系。自动变量是最基础的快捷方式,预定义变量是约定俗成的命名规范,模式规则帮你批量处理同类文件,静态模式规则则让你能精确控制哪些文件走哪条规则。
嗯,这一节的内容就到这儿。记住,变量和规则是Makefile的骨架,用好了它们,你的构建脚本会变得既简洁又强大。下次写Makefile的时候,不妨试试用 $@ 和 $< 替换掉那些硬编码的文件名——你会爱上这种清爽的感觉。