Makefile入门指南
Makefile 是一种用于自动化构建程序的工具脚本,核心作用是定义项目的编译规则,通过分析文件依赖关系,仅重新编译修改过的文件,从而大幅提高项目构建效率。尤其在多文件项目中,手动输入编译命令(如 gcc a.c b.c -o app)会非常繁琐,而 Makefile 可以通过 make 命令一键完成构建。
Makefile 本身是一个文本文件,不需要下载 —— 它是你根据项目编译规则自己编写的脚本文件。真正需要的是执行 Makefile 的工具 make(通常称为 “make 工具”),这个工具可能需要安装,但多数系统会预装。
先搞清楚:Makefile vs make 工具
- Makefile:是你手动编写的文本文件(类似
.txt),里面定义了编译规则(比如如何从.c文件生成可执行文件)。 - make 工具:是一个程序,用来读取并执行 Makefile 中的规则,自动完成编译、链接等操作。
Linux会预装make,Windows中使用mingw32-make。
一、Makefile 基本结构
Makefile 的核心是规则(Rule),一个完整的规则格式如下:
目标(target): 依赖项(prerequisites)
命令(command) # 命令前必须是 Tab 键(不能用空格)
- 目标(target):通常是要生成的文件(如
.o目标文件、可执行文件),也可以是 “伪目标”(如clean,用于执行清理操作)。 - 依赖项(prerequisites):生成目标所需要的文件或其他目标(如编译
.o文件需要对应的.c文件)。 - 命令(command):从依赖项生成目标的具体操作(如
gcc -c a.c -o a.o)。
二、核心概念与基础用法
1. 最简单的 Makefile
假设项目有 main.c 和 tool.c 两个源文件,要编译为可执行文件 app,Makefile 可写为:
# 目标:app;依赖:main.o 和 tool.o
app: main.o tool.o
gcc main.o tool.o -o app # 链接生成可执行文件
# 目标:main.o;依赖:main.c
main.o: main.c
gcc -c main.c -o main.o # 编译 main.c 为目标文件
# 目标:tool.o;依赖:tool.c
tool.o: tool.c
gcc -c tool.c -o tool.o # 编译 tool.c 为目标文件
执行 make 命令时,会自动按规则从依赖项生成目标:先编译 .c 为 .o,再链接 .o 为 app。
2. 伪目标(.PHONY)
如果目标不是实际文件(如 clean 用于删除编译产物),需要声明为伪目标,避免当前目录存在同名文件时 make 误判。示例:
# 声明 clean 为伪目标
.PHONY: clean
# 清理编译生成的文件
clean:
rm -f app *.o # 删除可执行文件和所有 .o 文件
执行 make clean 即可触发清理操作。
3. 变量(Variables)
变量用于简化 Makefile 编写(避免重复书写编译器、编译选项等),定义格式:变量名=值,引用格式:$(变量名)。
常见预定义变量:
CC:默认编译器(通常是cc,可手动指定为gcc)。CFLAGS:编译选项(如-Wall开启警告,-I指定头文件路径)。
CC = gcc # 指定编译器为 gcc
CFLAGS = -Wall -g # 编译选项:开启警告 + 调试信息
app: main.o tool.o
$(CC) $^ -o $@ # $^ 表示所有依赖,$@ 表示目标
main.o: main.c
$(CC) $(CFLAGS) -c $< -o $@ # $< 表示第一个依赖
tool.o: tool.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f app *.o
-
及时变量(简单赋值,
:=):变量在定义时立即展开并确定值,后续对被引用变量的修改不会影响当前变量。 -
延时变量(递归赋值,
=):变量在被使用时才展开并确定值,而非定义时。每次使用变量时,都会重新计算其值,因此后续对被引用变量的修改会影响当前变量。 - 条件赋值(
?=),仅当变量未被定义过时才赋值,若已定义则不生效(无论之前是:=还是=)。
A := 已存在的值
A ?= 新值 # A 已定义,此赋值无效
B ?= 我是新的 # B 未定义,赋值生效
all:
@echo A=$(A) # 输出 A=已存在的值
@echo B=$(B) # 输出 B=我是新的
2. 追加赋值(+=)
用于给变量追加内容,保持原变量的展开特性(及时变量仍及时,延时变量仍延时)。示例:
# 及时变量追加
A := 123
A += 456 # 等价于 A := $(A) 456(立即展开,A 变为“123 456”)
# 延时变量追加
B = 123
B += 456 # 等价于 B = $(B) 456(保持延时,使用时展开)
C := 789
B += $(C) # B 最终逻辑:$(B) 456 $(C)
all:
@echo A=$(A) # 输出 A=123 456
@echo B=$(B) # 输出 B=123 456 789(使用时展开所有引用)
4. 自动变量(Automatic Variables)
自动变量用于简化命令中的依赖 / 目标引用,避免重复书写,常用如下:
$@:当前规则的目标(如app、main.o)。$<:当前规则的第一个依赖项(如main.c)。$^:当前规则的所有依赖项(如main.o tool.o)。$?:所有比目标新的依赖项(用于增量编译)。
5. 模式规则(Pattern Rules)
当多个目标的编译规则相似时(如所有 .c 生成 .o),可使用模式规则统一处理,格式:%.o: %.c(% 为通配符)。
示例(用模式规则替代单个 .o 规则):
CC = gcc
CFLAGS = -Wall -g
# 所有 .o 文件的通用编译规则
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 链接规则
app: main.o tool.o
$(CC) $^ -o $@
.PHONY: clean
clean:
rm -f app *.o
此时,任何 .c 文件都能通过该规则生成对应的 .o,无需单独定义。
函数
在 Makefile 中,函数用于处理字符串、文件路径、循环逻辑等,扩展了 Makefile 的灵活性。函数调用格式为
$(函数名 参数) 或 ${函数名 参数}
参数间用逗号分隔(注意参数内的空格可能被保留)。以下是常用函数的分类说明:
一、字符串处理函数
1. subst:字符串替换
- 格式:
$(subst from,to,text) - 功能:将
text中所有from字符串替换为to。 - 示例:
$(subst abc,123,abcxyzabc) # 结果:123xyz123
2. patsubst:模式替换(支持通配符)
- 格式:
$(patsubst pattern,replacement,text) - 功能:将
text中符合pattern(可含%通配符)的字符串替换为replacement(%对应原字符串的匹配部分)。 - 示例:
$(patsubst %.c,%.o,a.c b.c) # 结果:a.o b.o(将所有.c文件替换为.o) $(patsubst x%,y%,x1 x2) # 结果:y1 y2(替换前缀x为y)
二、文件路径处理函数
1. dir:提取目录部分
- 格式:
$(dir names) - 功能:返回文件名列表
names中每个文件的目录部分(以/结尾,无目录则返回./)。 - 示例:
$(dir src/a.c b.c) # 结果:src/ ./(src/a.c的目录是src/,b.c的目录是当前目录./)
2. notdir:提取文件名部分
- 格式:
$(notdir names) - 功能:返回文件名列表
names中每个文件的文件名(去除目录部分)。 - 示例:
$(notdir src/a.c b.c) # 结果:a.c b.c
3. basename:去除后缀
- 格式:
$(basename names) - 功能:去除文件名列表
names中每个文件的后缀(最后一个.后的部分)。 - 示例:
$(basename a.c b.txt) # 结果:a b
4. addsuffix / addprefix:添加后缀 / 前缀
- 格式:
$(addsuffix suffix,names)/$(addprefix prefix,names) - 功能:给
names中的每个文件名添加suffix后缀或prefix前缀。 - 示例:
$(addsuffix .c,a b) # 结果:a.c b.c(添加后缀) $(addprefix src/,a.c) # 结果:src/a.c(添加前缀)
三、文件匹配与过滤函数
1. wildcard:匹配实际文件
- 格式:
$(wildcard pattern) - 功能:返回当前目录中符合
pattern(如*.c)的所有实际存在的文件列表。 - 示例:
SRCS = $(wildcard *.c) # 若当前目录有a.c、b.c,则SRCS=a.c b.c
2. filter:保留符合模式的文件
- 格式:
$(filter pattern...,text) - 功能:从
text中保留所有符合pattern(可多个模式)的字符串。 - 示例:
$(filter %.c %.h,a.c b.o c.h) # 结果:a.c c.h(保留.c和.h文件)
3. filter-out:排除符合模式的文件
- 格式:
$(filter-out pattern...,text) - 功能:从
text中排除所有符合pattern的字符串(与filter相反)。 - 示例:
$(filter-out %.o,a.c b.o c.h) # 结果:a.c c.h(排除.o文件)
四、循环与逻辑函数
1. foreach:循环处理列表
- 格式:
$(foreach var,list,text) - 功能:遍历
list中的每个元素,将其赋值给变量var,然后执行text(可引用var),最终拼接所有text的结果。 - 示例:
$(foreach f,a b,c$(f)) # 遍历a、b,分别执行c$f,结果:ca cb
2. if:条件判断
- 格式:
$(if condition,then-part[,else-part]) - 功能:若
condition非空,则返回then-part;否则返回else-part(可选)。 - 示例:
$(if a,true,false) # 结果:true(condition为a,非空) $(if ,true,false) # 结果:false(condition为空)
五、执行与调用函数
用于执行 Shell 命令、调用自定义逻辑等。
1. shell:执行 Shell 命令
- 格式:
$(shell command) - 功能:执行 Shell 命令
command,返回命令输出(换行符会被转为空格)。 - 示例:
FILES = $(shell ls *.txt) # 等价于wildcard,但依赖Shell环境 DATE = $(shell date +%Y%m%d) # 获取当前日期,如20251109
2. call:调用自定义变量(模拟函数)
- 格式:
$(call var,param1,param2...) - 功能:将变量
var作为 “函数模板”,用param1, param2...替换模板中的$(1), $(2)...(参数占位符)。 - 示例:
# 定义模板变量(参数$(1)、$(2)为占位符) template = $(1)_$(2).txt # 调用模板,传递参数a和b result = $(call template,a,b) # result的值为a_b.txt
3. eval:动态执行 Makefile 语法
- 格式:
$(eval text) - 功能:将
text作为 Makefile 语法解析并执行(可动态生成规则或变量)。 - 示例:
# 动态生成规则:a.o: a.c rule = a.o: a.c $(eval $(rule)) # 执行后,Makefile中会添加规则a.o: a.c
六、变量相关函数
用于查询变量的来源或状态。
origin:查询变量来源
- 格式:
$(origin var) - 功能:返回变量
var的定义来源,常见结果:undefined:未定义;file:在当前 Makefile 中定义;environment:来自环境变量;default:Make 的默认变量(如CC)。
- 示例:
$(origin CC) # 若未自定义CC,返回default(默认值为cc)
常用组合示例
实际使用中,函数常组合使用,例如自动生成目标文件列表:
# 1. 获取所有.c源文件
SRCS = $(wildcard src/*.c)
# 2. 将.c文件路径转换为.o文件(如src/a.c → obj/a.o)
OBJS = $(patsubst src/%.c,obj/%.o,$(SRCS))
# 3. 编译所有.o文件
all: $(OBJS)
obj/%.o: src/%.c
gcc -c $< -o $@
通过这些函数,可大幅简化 Makefile 的编写,尤其在处理大量文件或动态逻辑时非常高效。
三、进阶用法
1. 函数(Functions)
Makefile 提供内置函数用于文件查找、字符串处理等,常用函数:
wildcard:查找匹配的文件,如$(wildcard *.c)会返回当前目录所有.c文件。patsubst:字符串替换,如$(patsubst %.c,%.o,$(SRC))会把.c替换为.o。
示例(自动获取源文件和目标文件列表):
CC = gcc
CFLAGS = -Wall -g #开启警告 生成调试信息
# 自动获取所有 .c 源文件
SRC = $(wildcard *.c) # 假设返回 main.c tool.c
# 自动生成对应的 .o 目标文件(main.o tool.o)
OBJS = $(patsubst %.c,%.o,$(SRC))
app: $(OBJS)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f app $(OBJS)
此时新增 .c 文件,无需修改 Makefile,make 会自动识别并编译。
2. 嵌套 Makefile(子目录编译)
大型项目常按模块拆分到子目录(如 src/、lib/),主 Makefile 可通过 make -C 子目录 调用子目录的 Makefile。
示例(主 Makefile 调用 src/ 子目录):
# 主 Makefile
SUBDIRS = src # 子目录列表
# 遍历子目录执行 make
all:
for dir in $(SUBDIRS); do \
make -C $$dir; \ # -C 进入子目录执行 make
done
# 遍历子目录执行 make clean
clean:
for dir in $(SUBDIRS); do \
make -C $$dir clean; \
done
.PHONY: all clean
子目录 src/Makefile 可按常规写法编写(如编译 src/ 下的文件)。
3. 条件判断(Conditionals)
根据不同条件(如操作系统、编译模式)执行不同规则,常用语法:
ifeq (值1, 值2):判断值 1 和值 2 是否相等。ifneq (值1, 值2):判断值 1 和值 2 是否不等。
示例(区分 debug/release 模式):
CC = gcc
OBJS = main.o tool.o
# 若指定 make debug,则开启调试模式
ifeq ($(mode), debug)
CFLAGS = -Wall -g # 调试模式:保留调试信息
else
CFLAGS = -Wall -O2 # 默认 release 模式:优化编译
endif
app: $(OBJS)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f app $(OBJS)
执行 make mode=debug 会按调试模式编译,make 则按 release 模式编译。
4. 包含其他文件(include)
通过 include 指令可引入其他 Makefile(如公共配置、子模块规则),实现模块化管理。
示例:
# 引入公共编译选项
include common.mk
# 引入子模块规则
include src/sub.mk
app: $(OBJS)
$(CC) $^ -o $@
总结
Makefile 通过规则定义、变量、函数等机制,实现了项目的自动化、增量构建,是 C/C++ 项目中最常用的构建工具。掌握其基础规则和进阶用法,可大幅提升大型项目的开发效率。

浙公网安备 33010602011771号