第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真正“编程”的开始。