📚 目录(TOC)
第 1 节:Make 与 Makefile 基本概念
第 2 节:变量、自动变量与模式规则
第 3 节:条件判断(ifeq / ifdef 等)
第 4 节:Makefile 常用函数(wildcard / patsubst 等)
第 5 阶段:make 命令行选项与变量覆盖
今日学习总结
第 1 节:Make 与 Makefile 基本概念
核心总结
make是一个“自动化构建工具”,会根据Makefile里的目标 + 依赖 + 命令自动决定编译哪些文件。相比直接写
gcc main.c -o main,Makefile 的优势在于:增量编译、统一管理规则、一条命令完成多步操作。基本语法三要素:
目标: 依赖1 依赖2 ... 命令1 命令2
讲解内容
目标(target)
可以是最终生成的可执行文件,如
app可以是中间文件,如
main.o也可以是“伪目标”,例如
clean,只表示一系列操作,不对应真实文件。
依赖(prerequisites)
当前目标生成所依赖的文件列表。
当依赖文件比目标“新”时,make 会重新执行命令更新目标。
命令(command)
真正执行的 shell 命令,一定要以Tab开头。
支持任意 shell 命令:
gcc、rm、cp、脚本等。
make 的工作方式(简化理解)
找到指定的目标(缺省为 Makefile 中的第一个目标);
检查依赖是否存在、是否比目标新;
如需要,则执行规则中的命令;
对每个依赖递归重复上述过程。
示例代码
简单单文件工程:
# 文件:Makefile app: main.o gcc main.o -o app main.o: main.c gcc -c main.c -o main.o clean: rm -f app main.o使用方式:
make # 默认目标是 app make clean # 只执行 clean 目标输出说明
第一次执行make:
gcc -c main.c -o main.o gcc main.o -o app再次执行make(没有修改main.c):
make: 'app' is up to date.说明 make 检查到app不比main.c旧,无需重新编译,这就是增量构建。
错误示例
app: main.o gcc main.o -o app # 这里没有 Tab main.o: main.c gcc -c main.c -o main.o # 这里是空格,不是 Tab执行:
make错误原因
Makefile 中命令行必须以Tab开头,而不是空格。
上面的写法会出现类似错误:
missing separator。
正确写法
app: main.o gcc main.o -o app main.o: main.c gcc -c main.c -o main.o一条命令一行,前面保证是 Tab。
第 2 节:变量、自动变量与模式规则
核心总结
用变量统一管理编译器、编译选项和对象文件列表,可以极大减少重复。
自动变量
$@、$^、$<在规则内部代表“目标”、“所有依赖”、“第一个依赖”。模式规则
%.o : %.c可以一条规则生成所有.o文件,用于多文件工程。
讲解内容
普通变量
CC := gcc CFLAGS := -Wall -O2 OBJS := main.o utils.o TARGET := app在后续规则中直接使用$(变量名)即可。
自动变量
$@:当前规则的目标名$^:当前规则的所有依赖$<:当前规则的第一个依赖
模式规则
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@表示:所有.c文件都可以通过这个规则生成对应的.o文件。
示例代码
# 文件:Makefile CC := gcc CFLAGS := -Wall -O2 OBJS := main.o utils.o TARGET := app $(TARGET): $(OBJS) $(CC) $(CFLAGS) $^ -o $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(TARGET) $(OBJS)使用:
make # 构建 app make clean # 清理输出说明
第一次执行make:
gcc -Wall -O2 -c main.c -o main.o gcc -Wall -O2 -c utils.c -o utils.o gcc -Wall -O2 main.o utils.o -o app根据依赖关系,先生成所有.o文件,再链接成app。
错误示例
OBJS = main.o, utils.o # 使用了逗号 $(TARGET): $(OBJS) $(CC) $(CFLAGS) $^ -o $@执行make时会发现只找到一个main.o,,utils.o被当成第二个目标或无效内容。
错误原因
OBJS列表中不需要逗号,文件名用空格分隔即可。错误写法导致
OBJS实际只有一个名字为main.o,的文件,构建规则会失败。
正确写法
OBJS = main.o utils.o第 3 节:条件判断(ifeq / ifdef 等)
核心总结
Makefile 支持简单的条件编译,用于根据变量值或是否定义来切换编译选项、启用/禁用特性。
常用指令:
ifeq、ifneq、ifdef、ifndef、else、endif。典型场景:
DEBUG开关、根据操作系统选择不同的命令或库。
讲解内容
基于值的判断
DEBUG ?= 0 ifeq ($(DEBUG),1) CFLAGS += -g -O0 else CFLAGS += -O2 endif当
DEBUG=1时,开启调试信息、关闭优化;否则使用优化选项。
基于是否定义的判断
ifdef USE_MATH LIBS += -lm endif如果USE_MATH被定义了,就链接数学库。
ifneq / ifndef
ifneq ($(OS),Windows):非某个值ifndef VAR:VAR 未定义时执行代码块
示例代码
CC := gcc CFLAGS := -Wall DEBUG ?= 0 ifeq ($(DEBUG),1) CFLAGS += -g -O0 else CFLAGS += -O2 endif SRC := main.c OBJS := main.o TARGET := app $(TARGET): $(OBJS) $(CC) $(CFLAGS) $^ -o $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(TARGET) $(OBJS)命令行:
make # 使用 -O2 优化 make DEBUG=1 # 使用 -g -O0 调试选项输出说明
make:编译命令中会出现-Wall -O2make DEBUG=1:编译命令变为-Wall -g -O0
错误示例
ifeq (DEBUG,1) # 少了 $(),并且逗号两边空格不一致 CFLAGS += -g endif错误原因
ifeq结构正确写法是:ifeq (A,B)或ifeq ($(VAR),value),比较的是文本展开后的字符串;上例中写成
ifeq (DEBUG,1),实际上比较的是字符串DEBUG和1,永远不相等。
正确写法
ifeq ($(DEBUG),1) CFLAGS += -g endif或者保证两边都用变量/常量的一致写法:
DEBUG ?= 0 ifeq ($(DEBUG),1) ... endif第 4 节:Makefile 常用函数(wildcard / patsubst 等)
核心总结
Makefile 提供一批“文本处理函数”,常用来批量生成文件列表和做路径替换。
重点函数:
$(wildcard pattern):按模式匹配文件$(patsubst pattern,replacement,text):模式替换$(subst from,to,text):简单字符串替换$(addprefix prefix,names...)/$(addsuffix suffix,names...)$(dir files...)/$(notdir files...)
讲解内容
wildcard:自动收集源文件
SRC := $(wildcard src/*.c)会把src/目录下所有.c文件列出来。
patsubst:从源文件生成目标文件列表
OBJ := $(patsubst src/%.c, build/%.o, $(SRC))把src/foo.c转成build/foo.o。
addprefix / addsuffix
HDR := a.h b.h c.h INC := $(addprefix include/, $(HDR)) # 结果:include/a.h include/b.h include/c.hdir / notdir
FILES := src/main.c src/utils/io.c DIRS := $(dir $(FILES)) # 结果:src/ src/utils/ BASENAM := $(notdir $(FILES)) # 结果:main.c io.c示例代码
CC := gcc CFLAGS := -Wall -O2 SRC_DIR := src OBJ_DIR := build SRC := $(wildcard $(SRC_DIR)/*.c) OBJ := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC)) TARGET := app $(TARGET): $(OBJ) $(CC) $(CFLAGS) $^ -o $@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c @mkdir -p $(OBJ_DIR) $(CC) $(CFLAGS) -c $< -o $@ clean: rm -rf $(OBJ_DIR) $(TARGET)输出说明
SRC自动包含src/下所有.c;OBJ自动生成对应的build/*.o;make时会自动创建build/目录并进行编译。
错误示例
SRC := wildcard src/*.c # 少了 $() OBJ := patsubst src/%.c, build/%.o, $(SRC)错误原因
Makefile 函数必须写成
$(函数名 参数...);少了
$(),wildcard和patsubst只是普通字符串,不会被执行。
正确写法
SRC := $(wildcard src/*.c) OBJ := $(patsubst src/%.c, build/%.o, $(SRC))第 5 阶段:make 命令行选项与变量覆盖
核心总结
make命令本身有许多选项用来控制构建行为:-f file:指定 Makefile-C dir:先切换到指定目录再执行 make-n:只打印命令,不真正执行-i:忽略命令执行错误-s:静默模式,不打印命令本身-w:打印当前所在目录(进入/离开子目录时提示)-k:遇到错误时尽量继续构建其它不依赖该目标的任务
命令行可以对 Makefile 中的变量进行覆盖:
make VAR=value优先级最高。
讲解内容
指定 Makefile:
-f
make -f Makefile.debug make -f Makefile.release当目录下有多个 Makefile 时,使用-f明确选择一个。
切换目录:
-C
make -C src make -C build all等价于:
cd src && make cd build && make all预演构建:
-n
make -n make -n clean只打印即将执行的命令行,不真正执行操作,常用于检查clean或危险命令。
忽略错误:
-i与-k
-i:忽略所有命令错误,继续执行后续规则。-k:当某个目标失败时,尽量构建其它不依赖它的目标。
示例:
make -k all构建过程中某些目标失败,make 会记录错误,但不会立即停止。
一般情况下建议谨慎使用
-i,以免掩盖真正的构建问题。
静默模式:
-s
make -s make -s all只显示程序运行产生的输出,不打印gcc ...等命令本身,相当于在每个命令前加@。
显示目录信息:
-w
make -w make -C src -w会额外打印:
make: Entering directory '/path/to/src' ... make: Leaving directory '/path/to/src'以便在多层子目录构建时跟踪执行位置。
变量覆盖:命令行优先
# Makefile CC ?= gcc CFLAGS ?= -O2在命令行:
make CC=arm-linux-gnueabihf-gcc CFLAGS="-O0 -g"此时:
CC会被覆盖为交叉编译器;CFLAGS会使用命令行指定的调试参数。
示例代码
结合前面的 Makefile,只演示选项使用方式:
# 使用自定义 Makefile make -f Makefile.debug # 在 src 子目录中构建,但在项目根目录执行命令 make -C src all # 预演 clean,不真正删除 make -n clean # 遇到错误时尽量继续其它目标 make -k all # 静默模式,只看程序输出 make -s # 在命令行上覆盖变量 make CC=clang CFLAGS="-Wall -O0 -g"错误示例
make -D DEBUG=1 make --assign CC=clang错误原因
-D/--assign不是 GNU make 用来覆盖变量的选项;覆盖变量的正确方式是直接在命令行写
VAR=value;-e的含义是“让环境变量优先于 Makefile 中的设置”,并不是覆盖 Makefile 变量的首选方式。
正确写法
make DEBUG=1 make CC=clang CFLAGS="-Wall -O0 -g"今日学习总结
理解了
make与Makefile的基本关系与工作流程,掌握了“目标–依赖–命令”的核心结构。使用变量、自动变量和模式规则,提高了多文件工程中规则编写的复用性与可维护性。
通过
ifeq/ifneq/ifdef等条件判断,可以根据环境、开关变量灵活切换编译选项。利用
wildcard、patsubst等文本函数,可以自动收集源文件并生成对应的目标文件列表。掌握了
make -f/-C/-n/-i/-s/-w/-k等常用命令行选项,并能通过make VAR=value在命令行覆盖 Makefile 中的变量设置。