2. Makefile变量:变量的定义与引用、自动变量、环境变量、递归展开变量与简单展开变量
聊到Makefile,变量这东西是绕不开的。我刚开始写Makefile那会儿,总觉得变量就是用来偷懒的——少打几个字而已。后来项目大了才发现,变量用得好不好,直接决定了你的构建系统是「优雅」还是「灾难」。
今天咱们就把Makefile变量这块掰开揉碎了讲。嗯,内容不少,但都是干货。
2.1 变量的定义与引用
先说说最基本的。Makefile里定义变量,语法很简单:
CC = gcc
CFLAGS = -Wall -O2
TARGET = myapp
引用变量用 $(变量名) 或者 ${变量名}。我个人习惯用圆括号,看着更清晰:
$(TARGET): main.o utils.o
$(CC) $(CFLAGS) -o $(TARGET) main.o utils.o
这里有个坑——变量名大小写敏感。CC 和 cc 是两个不同的变量。我在项目中见过有人混用大小写,结果编译了半天找不到命令,排查了半小时才发现是变量名写错了。
2.2 自动变量
自动变量是Makefile里最实用的特性之一。说白了,就是Make帮你预定义好的一些变量,专门用在规则的命令行里。
常用的自动变量有这些:
| 变量 | 含义 | 示例值 |
|---|---|---|
$@ |
目标文件名 | main.o |
$< |
第一个依赖文件 | main.c |
$^ |
所有依赖文件(去重) | main.c utils.c |
$+ |
所有依赖文件(不去重) | main.c utils.c |
$? |
比目标新的依赖文件 | utils.c |
$* |
模式匹配中的主干部分 | main |
举个例子你就明白了:
%.o: %.c
$(CC) -c $< -o $@
这条规则的意思是:把每个.c文件编译成对应的.o文件。$< 自动取到当前匹配的.c文件,$@ 自动取到目标.o文件。你想想看,如果没有自动变量,你得写多少重复代码?
$@ 和 $<。链接规则里才用 $^。这样分工明确,不容易乱。
2.3 环境变量
Makefile可以读取系统的环境变量。比如 PATH、HOME、USER 这些。但这里有个容易踩的坑——环境变量和Makefile变量同名时,谁说了算?
默认情况下,Makefile里定义的变量会覆盖环境变量。但如果你用 -e 选项运行make,环境变量就会覆盖Makefile里的定义。
我曾经在一个项目里遇到过:某位同事在CI环境里设置了 CC=clang,但Makefile里写的是 CC=gcc。结果本地编译好好的,一上CI就报错。排查了半天才发现是环境变量在捣鬼。
我的建议是:
- 不要依赖环境变量来传递关键构建参数
- 如果必须用,用
?=来定义,这样只有变量未定义时才赋值
CC ?= gcc # 如果环境变量没设CC,就用gcc
2.4 递归展开变量与简单展开变量
这部分是Makefile变量的核心,也是很多人搞不清楚的地方。
简单展开变量用 := 赋值。它的特点是:在定义时就立即计算值。
X := hello
Y := $(X) world
X := hi
# 此时 Y 的值是 "hello world",不是 "hi world"
递归展开变量用 = 赋值。它的特点是:在使用时才计算值。
X = hello
Y = $(X) world
X = hi
# 此时 Y 的值是 "hi world",因为引用Y时才去展开X
为什么会这样?说白了,= 只是记了个公式,等到真正用的时候才去算。:= 则是当场就算好,以后不管变量怎么变,都不影响。
=(递归展开):延迟计算,可能造成无限递归:=(简单展开):立即计算,性能更好,行为可预测
我在项目中遇到过最经典的坑:
CFLAGS = -I$(INCDIR)
INCDIR = /usr/local/include
# 用 = 没问题,因为使用CFLAGS时才展开INCDIR
# 但如果写成这样:
CFLAGS := -I$(INCDIR)
INCDIR = /usr/local/include
# CFLAGS 就变成了 "-I",因为定义时INCDIR还没定义
所以我的建议是:
- 大部分场景用
:=,行为更可控 - 只有明确需要延迟展开时,才用
= - 绝对不要在
=里引用自身,会死循环
2.5 知识体系总览
下面这张图帮你理清Makefile变量的整体脉络:
2.6 实战建议
最后,分享几个我在实际项目中的经验:
- 变量命名要有规律:我习惯用大写表示工具链相关(CC, LD, AR),小写表示项目相关(sources, objects, target)
- 多用条件赋值:
?=是你的好朋友,既保留灵活性,又有默认值兜底 - 善用追加赋值:
+=可以在不破坏已有值的情况下追加内容,特别适合CFLAGS这种需要逐步构建的变量 - 复杂逻辑用函数:当变量赋值里出现
$(wildcard ...)、$(patsubst ...)时,考虑用:=提前计算好,避免每次使用都重复执行
嗯,Makefile变量这块就讲到这里。记住一句话:变量是Makefile的骨架,用好了事半功倍,用不好寸步难行。
:= 就别用 =,能用 ?= 就别硬编码,自动变量能省则省。
可可精品资料库