3、Makefile变量:变量的定义与使用、自动变量($@、$<、$^)、变量的赋值方式(=、:=、?=、+=)

说实话,我刚接触Makefile那会儿,最头疼的就是变量。看着一堆 $@、$^ 这种符号,心里直犯嘀咕——这玩意儿到底是啥?后来在项目里被坑了几次,才真正搞明白。今天咱们就把这块硬骨头啃下来。

3.1 变量的定义与使用

Makefile里的变量,说白了就是个名字替身。你定义一个变量,后面用到的地方直接引用它,改起来特别方便。

定义变量的语法很简单:

CC = gcc
CFLAGS = -Wall -g
TARGET = myapp

使用变量时,用 $(变量名) 或者 ${变量名} 都行:

$(CC) $(CFLAGS) -o $(TARGET) main.c

我个人习惯用圆括号,看着更顺眼。不过说实话,花括号也没毛病,看团队规范吧。

小技巧:变量名建议用大写,这样一眼就能看出哪些是变量。我在项目里见过有人用小写,结果跟目标名混在一起,排查起来特别费劲。

3.2 自动变量:$@、$<、$^

自动变量是Makefile里最实用的特性之一。你想想看,每次写规则都要重复写目标名、依赖名,多烦人?自动变量就是来解决这个问题的。

自动变量 含义 示例
$@ 当前规则的目标文件 如果目标是 main.o,那 $@ 就是 main.o
$< 第一个依赖文件 依赖列表里排最前面的那个
$^ 所有依赖文件(去重后) 把所有依赖拼在一起,空格分隔

看个实际例子就明白了:

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

# 等价于:
# gcc -c main.c -o main.o

这里 $< 展开成 main.c,$@ 展开成 main.o。省了多少打字功夫?

再来看个多文件的例子:

myapp: main.o utils.o
    gcc $^ -o $@

# 等价于:
# gcc main.o utils.o -o myapp

$^ 把两个 .o 文件都带上了,干净利落。

注意:$^ 会自动去重。如果依赖列表里有重复的文件名,它只保留一个。我曾经在项目里遇到过依赖写重复了,用 $^ 反而帮我避免了链接错误。

3.3 变量的赋值方式

嗯,这里要重点说说。Makefile有四种赋值方式,每种行为都不一样。搞混了,编译结果可能跟你预期完全不同。

3.3.1 = (递归展开赋值)

这种赋值方式,变量在使用时才展开。什么意思呢?看代码:

A = hello
B = $(A) world
A = hi

# 此时 B 的值是 "hi world",不是 "hello world"

为什么会这样?因为 B 在定义时并没有立即计算,而是等到真正用到 $(B) 的时候,才去查 A 当前的值。这时候 A 已经被改成 hi 了。

我个人不太推荐用 =,除非你很清楚自己在做什么。它容易引发一些诡异的bug,排查起来很头疼。

3.3.2 := (简单赋值)

这个就好理解了——立即展开。定义时是什么值,后面就固定了:

A := hello
B := $(A) world
A := hi

# 此时 B 的值是 "hello world",不受后面 A 变化的影响

我建议你默认都用 :=。行为直观,不容易出幺蛾子。我在团队里定规矩:除非有特殊需求,否则一律用 :=。

3.3.3 ?= (条件赋值)

只有变量未定义时才会赋值。如果已经定义过了,就跳过:

CC ?= gcc    # 如果 CC 没定义,就设为 gcc
CC ?= clang  # 这行不会生效,因为 CC 已经定义了

这个在写通用Makefile时特别有用。用户可以在命令行传参覆盖默认值:

# 命令行执行:make CC=clang
# 此时 Makefile 里的 CC ?= gcc 不会生效

3.3.4 += (追加赋值)

在原有值后面追加内容,中间自动加空格:

CFLAGS := -Wall
CFLAGS += -g
CFLAGS += -O2

# 最终 CFLAGS 的值是 "-Wall -g -O2"

这个我经常用来分层配置。基础选项放前面,调试选项、优化选项逐步追加,清晰明了。

核心总结:
  • := 立即展开,推荐默认使用
  • = 递归展开,小心使用
  • ?= 条件赋值,适合默认值
  • += 追加赋值,适合分层配置

3.4 知识体系结构图

下面这张图把本章的核心逻辑串起来了,你可以对照着回顾:

Makefile变量知识体系 Makefile变量 定义与使用 $(变量名) 或 ${变量名} 建议大写命名 自动变量 $@ 目标文件 $< 第一个依赖 $^ 所有依赖(去重) 赋值方式 = 递归展开(慎用) := 立即展开(推荐) ?= 条件赋值(默认值) += 追加赋值(分层) 实战建议 默认用 :=,自动变量多用,?= 设默认值,+= 做分层

3.5 避坑指南

最后分享几个我踩过的坑:

  • 变量名里不要有空格——CC = gcc 没问题,但 CC = gcc 等号两边有空格其实也行,但 CC= gcc 这种不对称写法容易出问题。我建议统一在等号两边各加一个空格,看着整齐。
  • 小心递归赋值导致死循环——比如 A = $(A) hello,用 = 会无限递归下去。用 := 就没这问题。
  • 自动变量只在规则的命令行中有效——别在依赖列表里用 $@,它不会展开成你想要的值。

好了,变量这块就讲到这里。记住::= 是你的好朋友,自动变量能省不少事,赋值方式选对了能避免很多坑。下次写Makefile的时候,试试用这些技巧,你会发现构建过程清爽很多。

我的习惯:每个Makefile开头先定义好 CC、CFLAGS、LDFLAGS、TARGET、SRCS、OBJS 这几个核心变量,后面写规则就轻松了。你可以试试这个套路。

可可精品资料库