第4章:变量与赋值——Makefile的“内存”与“计算”

各位同学,欢迎来到第四章。上一章我们聊了规则和依赖,那都是Makefile的骨架。今天我们要给这副骨架注入灵魂——变量。

说白了,变量就是Makefile里的“内存条”。你定义一次,到处引用。改一个值,全局生效。我刚开始写Makefile时,最烦的就是改一个路径要翻遍整个文件。后来学会了变量,嗯,真香。

4.1 变量的定义与赋值:四种“等号”的江湖

Makefile里赋值不是只有一种“=”。四种等号,四种脾气。我当年踩过的坑,今天帮你一一避开。

4.1.1 简单赋值(=)——递归展开

这是最基础的赋值方式。但它有个“坑”:变量在引用时才展开。

# 例1:递归展开的“陷阱”
A = hello
B = $(A) world
A = hi

all:
    @echo $(B)   # 输出:hi world,而不是 hello world

为什么会这样?因为B在赋值时,并没有立即把A的值“拍死”。它只是记了个引用。等到echo执行时,A已经变成了“hi”,所以B就变成了“hi world”。

⚠️ 避坑指南:我曾经在大型项目中用“=”定义了一堆相互依赖的变量,结果调试时发现值完全不对。后来排查了整整一下午,才发现是递归展开导致的。如果你希望变量值“拍死”在定义那一刻,请用“:=”。

4.1.2 简单赋值(:=)——立即展开

这个就好理解了。赋值的那一刻,右边的变量全部展开,结果直接存进去。

# 例2:立即展开,值被“拍死”
A := hello
B := $(A) world
A := hi

all:
    @echo $(B)   # 输出:hello world,A后来的变化不影响B

我个人习惯:能用“:=”就用“:=”。它让变量的行为更可预测,调试时少掉头发。

4.1.3 条件赋值(?=)——只有未定义时才赋值

这个很实用。你想给变量一个默认值,但又不想覆盖用户通过命令行传入的值。

# 例3:条件赋值
CC ?= gcc

# 如果用户在命令行执行:make CC=clang
# 那么CC就是clang,不会变成gcc

我在写跨平台项目时经常用这个。比如在Linux上默认用gcc,在macOS上默认用clang。用户想覆盖?没问题,命令行传参就行。

4.1.4 追加赋值(+=)——在原有值后面追加

这个最直观。就是在变量后面加东西,中间自动加空格。

# 例4:追加赋值
CFLAGS := -Wall -O2
CFLAGS += -g -DDEBUG

all:
    @echo $(CFLAGS)   # 输出:-Wall -O2 -g -DDEBUG

注意:如果变量之前是用“=”定义的,那么“+=”也会继承递归展开的特性。如果之前是用“:=”定义的,那么“+=”就是立即展开的追加。嗯,这里要注意一致性。

4.2 自动变量:Makefile里的“魔法符号”

自动变量是Makefile内置的“快捷方式”。它们不需要你定义,直接就能用。我刚开始学的时候觉得这些符号很神秘,用熟了之后发现——真离不开它们。

自动变量 含义 我常用的场景
$@ 目标文件的完整名称 编译命令中指定输出文件
$< 第一个依赖文件 单个源文件编译时指定输入
$^ 所有依赖文件(去重) 链接时列出所有目标文件
$? 所有比目标新的依赖文件 增量编译时只处理修改过的文件

4.2.1 $@ —— 目标文件自己

这个最简单。规则中的目标是什么,$@就是什么。

# 例5:$@的用法
main.o: main.c
    gcc -c $< -o $@
    # 等价于:gcc -c main.c -o main.o

4.2.2 $< —— 第一个依赖

当你只有一个源文件时,$< 就是那个源文件。配合 $@ 使用,堪称完美。

# 例6:$< 的典型用法
%.o: %.c
    gcc -c $< -o $@
    # 这条模式规则,会自动匹配所有.c文件

4.2.3 $^ —— 所有依赖(去重)

链接时最常用。把所有.o文件列出来,一股脑传给链接器。

# 例7:$^ 的用法
myapp: main.o utils.o io.o
    gcc $^ -o $@
    # 等价于:gcc main.o utils.o io.o -o myapp
💡 小技巧:$^ 会自动去重。如果你不小心在依赖中写了两次同一个文件,它只会出现一次。这个特性我特别喜欢,省得写脚本去重了。

4.2.4 $? —— 比目标新的依赖

这个变量用于增量编译。只有那些被修改过的文件,才会被处理。

# 例8:$? 的用法
lib.a: foo.o bar.o baz.o
    ar rcs $@ $?
    # 只打包那些比lib.a新的.o文件

我曾经在一个大型C++项目里,用 $? 实现了“只重新编译修改过的文件,然后增量链接”。编译时间从5分钟降到了30秒。嗯,这就是自动变量的威力。

4.3 综合示例:一个“正经”的Makefile

把上面这些知识点串起来,写一个实际可用的Makefile。你想想看,如果不用变量,这个文件得有多啰嗦。

# 一个正经的Makefile
CC := gcc
CFLAGS := -Wall -O2 -Iinclude
LDFLAGS := -lm

SRCS := main.c utils.c io.c
OBJS := $(SRCS:.c=.o)
TARGET := myapp

# 默认目标
all: $(TARGET)

# 链接
$(TARGET): $(OBJS)
    $(CC) $^ -o $@ $(LDFLAGS)

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

# 清理
clean:
    rm -f $(OBJS) $(TARGET)

# 伪目标
.PHONY: all clean

这个Makefile里,变量和自动变量都用上了。你看,$< 和 $@ 让编译规则变得极其简洁。$^ 让链接命令不用重复写一堆.o文件。

4.4 知识体系图

下面这张图,帮你理清本章的知识脉络。变量怎么定义、怎么赋值、自动变量怎么用,一目了然。

Makefile变量与赋值知识体系 变量与赋值 变量定义方式 自动变量 实际应用 = 递归展开 := 立即展开 ?= 条件赋值 += 追加赋值 $@ 目标 $< 首依赖 $^ 所有依赖 $? 新依赖 编译规则 链接规则 增量编译 模式规则 核心原则:能用 := 就用 :=,自动变量让规则更简洁 变量定义一次,到处引用;自动变量无需定义,直接使用

本章核心要点:

  • 四种赋值方式各有用途,“:=”最安全,“?=”适合默认值
  • 自动变量是Makefile的“语法糖”,能省则省
  • 变量和自动变量配合使用,能让Makefile变得简洁且可维护

好了,这一章的内容就到这里。变量这东西,说白了就是Makefile的“基础设施”。你用得越熟练,写出来的Makefile就越优雅。下一章我们会聊条件判断和函数,那才是Makefile真正“编程”的开始。

可可精品资料库