3、变量与赋值:变量的定义与引用、自动变量($@、$<、$^)、赋值运算符(=、:=、?=、+=)的区别
变量这东西,说白了就是 Makefile 的灵魂。没有变量,你写出来的东西就是一堆硬编码的路径和文件名,改一次能让你改到怀疑人生。我刚开始用 Make 的时候,就吃过这个亏——项目路径一换,整个 Makefile 几乎重写。后来我才明白,变量用得好,维护成本能降一大半。
3.1 变量的定义与引用
定义变量很简单,基本格式就是:
变量名 = 值
引用变量时,用 $(变量名) 或者 ${变量名}。我个人习惯用圆括号,看着更顺眼。
举个例子:
CC = gcc
CFLAGS = -Wall -O2
TARGET = myapp
$(TARGET): main.c utils.c
$(CC) $(CFLAGS) -o $(TARGET) main.c utils.c
你看,这样写的好处是,如果我想换编译器,只需要改 CC 的值,不用满文件去找 gcc 这个词。我在项目中遇到过好几次,客户要求换交叉编译工具链,这时候变量就派上大用场了。
3.2 自动变量:$@、$<、$^
自动变量是 Make 内置的「快捷方式」。它们不需要你定义,Make 会自动帮你填好值。嗯,这里要注意,自动变量只能在规则的命令行中使用,不能在依赖列表中用。
| 自动变量 | 含义 | 示例 |
|---|---|---|
$@ |
目标文件的完整名称 | 如果目标是 myapp,$@ 就是 myapp |
$< |
第一个依赖文件的名称 | 如果依赖是 main.c utils.c,$< 就是 main.c |
$^ |
所有依赖文件的名称,用空格分隔 | 如果依赖是 main.c utils.c,$^ 就是 main.c utils.c |
用自动变量改写上面的例子:
myapp: main.c utils.c
gcc -Wall -O2 -o $@ $^
是不是简洁多了?我曾经在一个大型项目中,把几十个目标文件的编译规则全部用自动变量重构了一遍,Makefile 从 300 行缩到了 80 行。你想想看,这种爽感,谁用谁知道。
$@ 和 $< 几乎是标配。
3.3 赋值运算符的区别
这是新手最容易踩坑的地方。四种赋值运算符,各有各的脾气。
3.3.1 = (递归展开赋值)
这是最基本的赋值方式。它的特点是:延迟展开。也就是说,变量在引用时才会计算值。
A = hello
B = $(A) world
A = hi
# 此时 B 的值是 "hi world",而不是 "hello world"
为什么会这样?因为 B 在定义时并没有立即计算 $(A) 的值,而是等到你真正用到 B 的时候,才去查找 A 当前的值。我在项目中遇到过一个问题:用 = 赋值时,不小心形成了循环引用,Make 直接报错。所以,用 = 要小心,别把自己绕进去。
3.3.2 := (简单赋值)
这个就好理解了:立即展开。定义时就把值算好,后面再怎么改前面的变量,都不影响。
A = hello
B := $(A) world
A = hi
# 此时 B 的值是 "hello world",不会变
我个人习惯,能用 := 的地方尽量用 :=。为什么?因为行为更可预测,不容易出幺蛾子。特别是定义路径、编译器选项这些固定内容时,:= 是最安全的。
= 定义了一个变量,然后又用这个变量去定义另一个变量,最后再改第一个变量的值——嗯,你可能会得到意想不到的结果。我曾经调试过一个 bug,查了半小时才发现是 = 的延迟展开搞的鬼。
3.3.3 ?= (条件赋值)
这个运算符的意思是:如果变量没有被定义过,才赋值。如果已经定义过了,就忽略。
CC ?= gcc
# 如果外部没有传入 CC,就用 gcc
# 如果外部传了 CC=clang,那就用 clang
这个在写通用 Makefile 时特别有用。比如你想让用户可以在命令行覆盖某些配置:
make CC=clang CFLAGS="-O3"
这时候 ?= 就能保证用户传入的值不会被 Makefile 内部的赋值覆盖掉。
3.3.4 += (追加赋值)
这个最简单,就是在原有值后面追加内容。注意,它会自动加一个空格。
CFLAGS = -Wall
CFLAGS += -O2
CFLAGS += -g
# 最终 CFLAGS 的值是 "-Wall -O2 -g"
我在项目中经常用 += 来分层构建编译选项。比如基础选项放在一个地方,调试选项放在另一个地方,最后用 += 合并起来。这样代码清晰,也方便维护。
3.4 知识体系总览
下面这张图,是我梳理的本章核心逻辑。你看一眼,基本就能把变量和赋值这块串起来了。
3.5 实战建议
说了这么多,最后给你几个我自己的经验:
- 能用
:=就别用=——除非你明确需要延迟展开的特性。我见过太多因为=导致的诡异 bug 了。 - 自动变量是你的好朋友——写通用规则时,
$@和$^能让你少写 80% 的重复代码。 ?=是给用户留的后门——如果你在写一个供他人使用的 Makefile,记得用?=定义关键变量,让用户可以灵活覆盖。+=适合分层管理——把基础配置、调试配置、发布配置分开写,最后用+=组合,清晰又好维护。
好了,变量和赋值这块就聊到这儿。记住一句话:变量用得好,Makefile 写得少。下一节我们聊聊条件判断和函数,那又是另一番天地了。