Linux操作系统从入门到实战(十)Linux开发工具(下)make/Makefile的推导过程与扩展语法
Linux操作系统从入门到实战(十)Linux开发工具(下)make/Makefile的推导过程与扩展语法
- 前言
- 一、 make/Makefile的推导过程
- 1. 先看一个完整的Makefile示例
- 2. make的工作流程
- (1)寻找Makefile文件
- (2)确定最终目标
- (3) 检查最终目标是否需要更新
- (4) 从最终目标到中间文件
- (5) 反向执行命令:从源文件到最终目标
- (6) 遇到错误立即停止
- (7) 核心逻辑:只做“必要的事”
- 二、make/Makefile的扩展语法
- 1. 变量的妙用
- 2. 自动变量
- 3. 批量处理
- (1)模式规则:一键编译所有 .c 文件
- (2)通配符函数:自动找文件、改名字
- 4. 高级小技巧
- 5. 完整示例
前言
- 前面的博客里我们讲解了Linux开发工具自动化构建-make/Makefile里的基础知识
- 接下来我们继续讲解Linux开发工具自动化构建-make/Makefile里的细节,make/Makefile的推导过程与扩展语法
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482
一、 make/Makefile的推导过程
1. 先看一个完整的Makefile示例
假设我们有一个名为myproc
的程序,对应的Makefile内容如下
# 最终目标:生成可执行文件myproc,依赖于中间文件myproc.o
myproc: myproc.ogcc myproc.o -o myproc # 通过链接myproc.o生成可执行文件myproc# 中间目标:生成目标文件myproc.o,依赖于汇编文件myproc.s
myproc.o: myproc.sgcc -c myproc.s -o myproc.o # 将汇编文件myproc.s编译为目标文件myproc.o# 中间目标:生成汇编文件myproc.s,依赖于预处理文件myproc.i
myproc.s: myproc.igcc -S myproc.i -o myproc.s # 将预处理文件myproc.i转换为汇编文件myproc.s# 中间目标:生成预处理文件myproc.i,依赖于源文件myproc.c
myproc.i: myproc.cgcc -E myproc.c -o myproc.i # 对源文件myproc.c进行预处理,生成myproc.i# 伪目标:清理所有编译过程中产生的中间文件和可执行文件
.PHONY: clean
clean:rm -f *.i *.s *.o myproc # 删除所有.i、.s、.o文件及myproc
执行make命令的输出
- 当我们在终端输入
make
后,会看到如下执行过程:
$ make
gcc -E myproc.c -o myproc.i # 第一步:预处理
gcc -S myproc.i -o myproc.s # 第二步:生成汇编
gcc -c myproc.s -o myproc.o # 第三步:生成目标文件
gcc myproc.o -o myproc # 第四步:链接生成可执行文件
整个过程就像“剥洋葱”——从最终目标出发,逐层拆解依赖,直到找到最原始的源文件,再反向执行编译命令。
- 下面我们详细解释make是如何一步步完成这个过程的。
2. make的工作流程
在默认情况下(即直接输入make
命令),工具的执行逻辑可以拆解为以下8个核心步骤:
(1)寻找Makefile文件
- make首先会在当前目录下搜索名为
Makefile
或makefile
的文件(注意大小写敏感,推荐统一使用Makefile
)。 - 如果找不到这两个文件,会直接报错“没有规则可制作目标”。
(2)确定最终目标
找到Makefile后,make会将文件中第一个目标作为“最终目标”。
- 在上面的例子中,第一个目标是
myproc
(可执行文件)。 - 因此make的最终任务就是生成
myproc
。
(3) 检查最终目标是否需要更新
确定最终目标后,make会通过两个条件判断是否需要生成/更新myproc
:
- 若
myproc
不存在:直接执行后续命令生成它; - 若
myproc
已存在:比较myproc
和它的依赖文件myproc.o
的修改时间。 - 如果
myproc.o
的修改时间比myproc
晚(即myproc.o
被更新过),则需要重新生成myproc
; - 反之,若
myproc
比myproc.o
新,说明myproc
已是最新,无需操作。
小技巧:可以用
touch 文件名
命令手动更新文件的修改时间(比如touch myproc.o
),测试make是否会重新执行命令。
(4) 从最终目标到中间文件
如果myproc
需要更新(或不存在),make会检查它的依赖myproc.o
:
- 若
myproc.o
不存在:在Makefile中寻找以myproc.o
为目标的规则(即myproc.o: myproc.s
这一行),然后根据规则生成myproc.o
; - 若
myproc.o
已存在:同样比较myproc.o
和它的依赖myproc.s
的修改时间,判断是否需要重新生成myproc.o
。
这个过程会逐层递归:
- 检查
myproc.s
是否存在/需要更新 → 依赖myproc.i
; - 检查
myproc.i
是否存在/需要更新 → 依赖myproc.c
(源代码文件)。
直到找到最底层的依赖myproc.c
——这是我们手动编写的源文件,必须存在(如果myproc.c
缺失,make会直接报错退出)。
(5) 反向执行命令:从源文件到最终目标
当确认所有依赖都已处理后,make会按照依赖链的反向顺序执行命令:
- 先执行
gcc -E myproc.c -o myproc.i
:将源文件myproc.c
预处理为myproc.i
; - 再执行
gcc -S myproc.i -o myproc.s
:将myproc.i
编译为汇编文件myproc.s
; - 接着执行
gcc -c myproc.s -o myproc.o
:将myproc.s
汇编为目标文件myproc.o
; - 最后执行
gcc myproc.o -o myproc
:将myproc.o
链接为可执行文件myproc
。
整个过程就像“链式反应”——只有前一个中间文件生成后,才能执行下一个步骤。
(6) 遇到错误立即停止
在依赖检查或命令执行过程中,若出现以下情况,make会直接退出并报错:
- 某个依赖文件(如
myproc.c
)不存在; - 命令执行失败(如编译错误,返回非0状态码)。
但需要注意:make只负责检查“依赖是否存在”和“命令是否执行”,不负责检查命令的语法正确性(比如把gcc
写成g++
,make会执行命令但因错误退出)。
(7) 核心逻辑:只做“必要的事”
make的高效性体现在“增量编译”——它只会重新生成“过时”的文件。例如:
- 若只修改了
myproc.c
:make会重新生成myproc.i
、myproc.s
、myproc.o
和myproc
; - 若只修改了
myproc.s
:make只会重新生成myproc.o
和myproc
,无需处理myproc.i
和myproc.c
; - 若所有文件都未修改:make会直接提示“
myproc
已是最新”,不执行任何命令。
二、make/Makefile的扩展语法
- 刚开始写 Makefile 时,我们可能会像下面这样写
code: code.ogcc code.o -o code # 链接:把 .o 变成可执行文件code.o: code.sgcc -c code.s -o code.o # 汇编:.s 变 .ocode.s: code.igcc -S code.i -o code.s # 编译:.i 变 .scode.i: code.cgcc -E code.c -o code.i # 预处理:.c 变 .iclean:rm -f *.i *.s *.o code # 清理垃圾文件
这看起来还行,但问题大了:
- 如果你把
code.c
改名叫main.c
,上面所有提到code
的地方都得改,漏一个就报错。 - 要是我们加了个新文件
tool.c
,又得复制粘贴一堆规则,累得慌。
1. 变量的妙用
如果把经常用的文件名、命令起个外号,改的时候只改外号,是不是就方便了?
- 这就是变量的作用。
比如下面这样:
BIN=code # 给可执行文件起个外号叫 BIN
CC=gcc # 给编译器起个外号叫 CC
SRC=code.c # 给源文件起个外号叫 SRC
FLAGS=-o # 给输出参数起个外号叫 FLAGS
RM=rm -f # 给删除命令起个外号叫 RM$(BIN):$(SRC) # 用外号代替具体名字$(CC) $(FLAGS) $(BIN) $(SRC) # 相当于 gcc -o code code.cclean:$(RM) $(BIN) # 相当于 rm -f code
- 现在如果要改文件名,比如把
code.c
改成main.c
,只需要改SRC=main.c
就行,其他地方不用动。
2. 自动变量
有时候规则里的文件名会重复。
- 比如
gcc -o code code.o
里,code
出现了两次。要是文件名很长,写起来超麻烦。这时候“自动变量”就派上用场了,它们能自动代表规则里的目标或依赖文件。
常用的有三个:
$@
:代表当前规则的“目标文件”(比如上面的code
)。$^
:代表当前规则的“所有依赖文件”(比如上面的code.o
)。$<
:代表当前规则的“第一个依赖文件”(比如只有一个依赖时,和$^
一样)。
举个例子:
$(BIN):$(OBJ) $(CC) -o $@ $^ # 相当于 gcc -o code code.o($@ 是 code,$^ 是 code.o)@echo "正在把 $^ 变成 $@" # 会打印:正在把 code.o 变成 code%.o:%.c # 后面会讲这个,先关注自动变量$(CC) -c $< # 相当于 gcc -c code.c($< 是 code.c)@echo "正在把 $< 变成 $@" # 会打印:正在把 code.c 变成 code.o
是不是像用了“占位符”?不用手写具体文件名,Makefile 自动帮你填,少写好多字。
3. 批量处理
如果你的项目有多个 .c
文件(比如 a.c
、b.c
、c.c
),总不能每个都写一条编译规则吧?这时候就需要“模式规则”和“通配符”来批量干活。
(1)模式规则:一键编译所有 .c 文件
模式规则用 %
当通配符,比如 %.o: %.c
表示:“所有 .o
文件都由对应的 .c
文件生成”。
%.o: %.c # 只要有 x.c,就自动生成 x.o$(CC) -c $< -o $@ # 对每个 .c 文件执行:gcc -c x.c -o x.o
%.o: %.c # 只要有 x.c,就自动生成 x.o
现在不管你有 a.c
、b.c
还是 c.c
,这条规则都能自动处理,不用一个一个写!
(2)通配符函数:自动找文件、改名字
还有两个超实用的工具:
wildcard
:帮你找出所有符合条件的文件。比如SRC=$(wildcard *.c)
,它会自动收集当前文件夹里所有.c
文件,不管有多少个。patsubst
:帮你批量改文件名。比如OBJ=$(patsubst %.c,%.o,$(SRC))
,意思是“把SRC
里所有.c
结尾的文件,改成.o
结尾”。
举个例子:如果当前有 a.c
、b.c
,那么:
SRC=$(wildcard *.c) # SRC 就等于 "a.c b.c"
OBJ=$(SRC:.c=.o) # 这是上面那句的简写,OBJ 就等于 "a.o b.o"
这下不用手动列所有文件了,Makefile 会自动帮你找齐。
4. 高级小技巧
命令前的 @ 和 -
- 命令前加
@
:只显示命令的结果,不显示命令本身。比如@echo "编译中..."
,只会打印“编译中…”,不会显示echo "编译中..."
,看起来更清爽。 - 命令前加
-
:就算命令执行失败,也继续往下跑。比如-rm -f *.o
,就算没有.o
文件,也不会报错中断。
5. 完整示例
最后来看一个能应付大多数小项目的 Makefile,我们一步步拆开看:
BIN=proc # 可执行文件名叫 proc
CC=gcc # 用 gcc 编译
SRC=$(wildcard *.c) # 自动找所有 .c 文件
OBJ=$(SRC:.c=.o) # 把 .c 换成 .o,比如 a.c → a.o
LFLAGS=-o # 链接时的输出参数
FLAGS=-c # 编译时的参数(-c 表示只编译不链接)
RM=rm -f # 删除命令# 第一步:链接所有 .o 文件,生成可执行文件 proc
$(BIN):$(OBJ) @$(CC) $(LFLAGS) $@ $^ # 相当于 gcc -o proc a.o b.o(自动找所有 .o)@echo "链接完成:把 $^ 变成了 $@" # 第二步:编译每个 .c 文件成 .o 文件
%.o:%.c @$(CC) $(FLAGS) $< # 相当于 gcc -c a.c(自动处理每个 .c)@echo "编译中:$< → $@"# 声明伪目标
.PHONY: clean test
clean: # 清理垃圾文件$(RM) $(OBJ) $(BIN) # 删除所有 .o 和 proc@echo "清理完毕!"test: # 测试变量内容@echo "源文件列表:$(SRC)" @echo "目标文件列表:$(OBJ)"
这个 Makefile 会干以下事情:
- 自动找出当前文件夹所有
.c
文件(比如a.c
、b.c
)。 - 自动算出需要生成的
.o
文件(a.o
、b.o
)。 - 逐个把
.c
编译成.o
(不用手动写每个规则)。 - 把所有
.o
链接成可执行文件proc
。 - 提供
make clean
清理垃圾,make test
查看文件列表。
以上就是这篇博客的全部内容,下一篇我们将继续探索Linux的更多精彩内容。
我的个人主页
欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482
非常感谢您的阅读,喜欢的话记得三连哦 |