文章目录
- 1.前置知识
-
- 1.什么是make工具
- 2.什么是Makefile?
- 2.设置vim首行缩进
- 3.Makefile基本语法
-
- 1.语法格式
- 2.伪目标
-
- 1.概述
- 2.伪目标格式
- 3.Makefile变量和变量赋值
- 4.自动化变量
- 4.Makefile函数
-
- 1.wildcard 函数
- 2.notdir 函数
- 3.dir 函数
- 4.patsubst 函数
- 5.foreach 函数
1.前置知识
1.什么是make工具
- make工具,编译辅助工具
- 解决什么问题:解决使用命令编译工程非常繁琐的问题,在大型项目中,如果修改了其中一个源文件,不需要重新编译整个项目的几十万个文件。Make会智能地只重新编译那些被修改过的文件及其依赖的文件,极大地节省编译时间。
- 工作原理:它通过比较文件修改时间来决定是否执行编译命令。如果“目标文件”不存在,或者“依赖文件”比“目标文件”新,Make就会执行对应的命令。
- 运行方式:在终端输入 make 命令即可运行。
2.什么是Makefile?
- Makefile 是给 Make 工具看的文本配置文件,它描述了整个工程编译链接等规则的文件,定义了项目中的依赖关系和编译命令。
- makefile的命令必须是makefile或Makefile
2.设置vim首行缩进
vi /etc/vim/vimrc (rc结尾的一般为配置 文件) 输入 set tabstop=4,保存后退出即可。此时vim打开后的缩进变成了4个空格
前后对比设置前
设置后 
3.Makefile基本语法
1.语法格式
语法格式:
目标:依赖
(tab)命令

补充说明:这里是tab键,而非空格,如果这里缩进不是四格,参考上述所讲设置vim首行缩进
目标:all 依赖:空 命令:gcc hello.c -o hello
效果示例:
也可以写成 
因为 all 依赖 hello.o 文件,所以要先执行 gcc -c hello.c 得到 hello.o 文件,然后才可以执行 gcc hello.c -o hello。所以输入 make 命令后执行顺序如下
补充说明
在编译的时候,用 make 目标来编译,如果不指定目标的话,默认执行的是第一个目标所对应的规则。也就是说 make 和 make all 是一样的。
2.伪目标
1.概述
问题:当前目标不能和Makefile目标名一样的文件,例如,当前目录下有个文件名是clean,同时Makefile存在目标名clean,那么执行make clean命令就会报错,因此引入伪目标,当使用伪目标来声明clean就可以解决这一问题,避免与当前目录下的同名文件发生冲突 
2.伪目标格式
.PHONY:目标
示例
执行 make clean 命令。尽管当前目录下有 clean 同名文件,make clean 命令也可以执行成功 
3.Makefile变量和变量赋值
- 定义:变量名 = 值(等号两边有没有空格都可以,但推荐不加空格,如 CC=gcc)
- 引用:必须写成 $(变量名) 或 ${变量名}
四种赋值方式
| = | 递归展开赋值 | “用到时再展开”(延迟展开),如果后面变量值变了,前面的引用也会跟着变。 |
| := | 简单展开赋值 | “定义时立即展开”,赋值瞬间就把右边的值算好固定下来 |
| ?= | 条件赋值 | “仅当变量为空时才赋值”,如果前面已经赋过值,这一行就不生效 |
| += | 追加赋值 | “在原有值后面追加内容”,自动加空格连接 |
- 示例1,=来赋值
运行效果 
使用=来给变量赋值,用到时再展开,延时赋值,变量的值为最后被赋值的值,即为3,所以这里最后为32
- 示例2,:=来赋值
运行效果 
使用:=来给变量赋值,定义时立即展开,是立即赋值,在执行a:=1时变量值就已经确定,所以最后打印伪12,而非32
- 示例3,使用?=来赋值 情景1
运行效果
情景2
运行效果 
使用?=来给变量赋值,仅当变量为空时才赋值,如果 变量前面没有被赋值,那么就给它赋值,如果前面已经赋值了,就使用前面的值,所以情景1为12,情景2为42
- 示例4,使用+=来赋值
运行效果 
使用+=来赋值,追加赋值 ,原有值后面追加内容自动加空格连接
如果赋值很长,也可以使用换行符 /
运行效果 
4.自动化变量
| $@ | 目标(Target) | 代表当前规则中冒号左边的目标文件名 |
| $< | 第一个依赖如果依赖模式是%,那么它就表示一系列文件。(%为通配符,类似 linux 上的*) | 代表当前规则中冒号右边的第一个依赖文件 |
| $^ | 全部依赖 | 代表当前规则中冒号右边的所有依赖文件列表 |
使用自动化变量简化前
使用自动化变量简化后
自动化变量的作用就是:让 Makefile 中的“命令部分”彻底脱离具体文件名,使得规则变得通用、动态、零重复且不易出错。从之前写死的 gcc hello.c -o hello,升级到 gcc $< -o $@
4.Makefile函数
1.wildcard 函数
wildcard 函数在 Makefile 中的作用,一句话概括就是:帮你“自动在目录里扫描文件”,让你彻底告别手动列出源文件名的笨拙做法。
你现在写的 hello: hello.o main.o 是写死的。如果明天你新增了 utils.c,你必须手动改成 hello: hello.o main.o utils.o。而有了 wildcard,它会自动把目录下所有匹配的文件都找出来。
$(wildcard 匹配模式)
- 匹配模式:支持 *(任意字符串)和 ?(单个字符)通配符。
示例:
var = $(wildcard *.c)
- 如果当前目录下有 hello.c、main.c、utils.c,那么 var 的值自动变成 hello.c main.c utils.c的路径,如./hello.c
- 如果只有 hello.c,那么 var 就是 hello.c的文件路径,如./hello.c
2.notdir 函数
notdir 函数的作用一句话概括就是:把文件路径前面的“文件夹部分”砍掉,只留下纯粹的文件名(含后缀)
基本语法
$(notdir 带路径的文件名列表)
- 它会逐个处理列表中的名字,去掉最后一个 / 之前(含 /)的所有字符。
效果示例(输入 → 输出)
假设你通过 wildcard 扫描得到了一堆带路径的文件名:
输入(带路径)经过 $(notdir …)输出(纯文件名)
./src/hello.c →hello.c
./src/main.c →main.c
./lib/utils/helper.c →helper.c
./hello.c →hello.c
../project/main.c →main.c
SRCS := $(wildcard src/*.c) # 得到 "./src/hello.c src/main.c"
此时,如果想用 %.o: %.c 的通用规则编译,并生成 .o 文件放在当前目录下,你需要把./src/去掉,否则会出乱子。
3.dir 函数
dir 函数的作用与 notdir 正好相反,一句话概括:提取文件路径中的“文件夹部分”,保留最后的斜杠 /
基本语法
$(dir 带路径的文件名列表)
- 它会逐个处理列表中的名字,截取最后一个/之前(含 /)的所有字符
效果示例(输入 → 输出)
输入(文件路径)经过 $(dir …)输出(目录部分)
./src/hello.c→./src/
./src/main.c→./src/
./lib/utils/helper.c→./lib/utils/
./hello.c→./
./hello.c→./
../project/main.c→../project/
4.patsubst 函数
patsubst 函数的作用一句话概括:它可以根据你定义的“模式”,批量把文件名列表中的后缀换掉, 最常见的用途就是把 .c 列表变成 .o 列表
基本语法
$(patsubst 原始模式, 目标模式, 待替换的文本列表)
- 原始模式:通常包含通配符 %,代表任意长度的字符串。
- 目标模式:通常包含同样的 %,代表把匹配到的内容原样保留。
- 匹配规则:% 匹配任意字符串,替换时会把 % 匹配到的内容“搬运”到目标模式中
效果示例(输入 → 输出)
待替换的文本原始模式目标模式输出结果
hello.c%.c %.ohello.o
src/main.c%.c %.osrc/main.o
hello.c main.c%.c %.ohello.o main.o
src/hello.c src/%.cbuild/%.obuild/hello.o
替换我们可以使用这个函数,也可以使用$( 待替换的文本列表:旧后缀=新后缀)这个格式来替换,效果相同,例如$(hello.c:%.c=%.o),输出结果为hello.o
5.foreach 函数
foreach 函数是 Makefile 中的循环函数,它的作用就是“遍历列表,逐个处理”
基本语法
$(foreach 变量名, 待遍历的列表, 要执行的操作文本)
- 变量名:临时变量,每次循环时,它会依次取得列表中的一个元素值。
- 待遍历的列表:用空格分隔的单词列表。
- 要执行的操作文本:每次循环时要执行的表达式,通常会引用临时变量(加 $)。
- 返回值:所有循环执行结果的集合(用空格分隔)。
效果示例(输入 → 输出) 示例 1:在每个文件名后加后缀
FILES := hello main utils
RESULT := $(foreach file,$(FILES),src/$(file).c)
执行过程:
- 第 1 次:file=hello,操作 → src/hello.c
- 第 2 次:file=main,操作 → src/main.c
- 第 3 次:file=utils,操作 → src/utils.c
- 最终返回值:src/hello.c src/main.c src/utils.c







