【Linux】深入浅出 Linux 自动化构建:make 与 Makefile 的实用指南
🔥 脏脏a的技术站 🔥
「在代码的世界里,脏脏的技术探索从不设限~」
🚀 个人主页:脏脏a-CSDN博客
📌 技术聚焦:Linux 环境下 yum 与 vim 工具的实用技巧与深度解析
📊 文章专栏:Linux🔗 上篇回顾:sudo 白名单配置与 GCC/G++ 编译器使用指南
在 Linux 开发中,make和Makefile是提升项目构建效率的神兵利器。从单个文件的小程序到百万行代码的大型工程,掌握它们能让你从重复的编译命令中解放出来。本文将带你从零开始理解make、makefile 的工作原理,并通过实战案例掌握其应用技巧。
目录
🎨一、背景:为什么需要 make/Makefile?
🧪二、核心概念:依赖关系与依赖方法
【案例】:一个简单的 C 项目
【问题】:文件名必须是makefile吗?
【问题】:如果打乱依赖关系的顺序会正常执行吗?
📝三、make 的工作原理:如何判断 “是否需要编译”?
【stat指令】
🔍四、伪目标与项目清理
🚧五、特殊符号
📋六、依赖关系缺失问题
🔗七、如何在使用make后不显示依赖方法
📌八、make工作原理总结
🎨一、背景:为什么需要 make/Makefile?
想象一下,你有一个由多个.c文件组成的项目,每次修改代码后都要手动执行gcc -c file1.c、gcc -c file2.c、gcc -o target file1.o file2.o…… 这样的重复劳动不仅低效,还容易出错。
make是一个命令工具,Makefile是一个规则文件,二者配合实现了自动化编译:只需编写一次规则,后续执行make命令即可自动完成 “哪些文件需要编译、哪些需要重编译” 的判断,极大提升开发效率。
对于大型工程,会不会写 Makefile 甚至成为衡量工程师能力的一个侧面标准 —— 毕竟它能清晰管理 “文件依赖、编译顺序、清理逻辑” 等复杂场景。
🧪二、核心概念:依赖关系与依赖方法
makefile 的核心是 “依赖关系”和“依赖方法”:
- 依赖关系:描述 “目标文件” 依赖于哪些 “源文件”。例如,可执行文件
hello依赖于目标文件hello.o,hello.o依赖于汇编文件hello.s,以此类推。- 依赖方法:描述如何从 “依赖文件” 生成 “目标文件”(通常是编译命令)。
【案例】:一个简单的 C 项目
我们以输出 “hello Makefile!” 的小程序为例,逐步构建 Makefile。
【步骤 1】:编写 C 代码(hello.c)
#include <stdio.h>
int main()
{printf("hello Makefile!\n");return 0;
}
【步骤 2】:编写 Makefile 规则
一个完整的 Makefile 规则格式为:
目标文件: 依赖文件依赖方法(编译命令,注意开头必须是Tab缩进)
针对hello.c,我们可以编写多层依赖的 Makefile:
# 最终可执行文件hello,依赖于hello.o
hello: hello.ogcc hello.o -o hello# 目标文件hello.o,依赖于hello.s
hello.o: hello.sgcc -c hello.s -o hello.o# 汇编文件hello.s,依赖于hello.i
hello.s: hello.igcc -S hello.i -o hello.s# 预处理文件hello.i,依赖于hello.c
hello.i: hello.cgcc -E hello.c -o hello.i
【步骤 3】:执行 make命令
在终端中输入make时,它会仅以 Makefile 中第一个定义的目标为起点,沿着该目标的完整依赖链(例如hello→hello.o→hello.s→hello.i→hello.c)执行构建。
整个过程中,make只会处理这个 “第一个目标” 及其所有依赖项(包括直接依赖和间接依赖),其他未被该依赖链关联的目标(即使在 Makefile 中定义)也不会被自动执行。
同时,它会通过比较 “目标文件” 与 “依赖文件” 的修改时间实现 “按需编译”:只有当依赖文件更新过(修改时间更新),才会重新编译对应的目标,否则直接跳过,大幅提升效率。

【问题】:文件名必须是makefile吗?
make是命令,makefile是一个文件,当前目录下的文件
文件名不一定必须是 Makefile,但 make 命令有默认的文件名查找规则,常用的合法文件名有两种:
Makefile(首字母大写)makefile(全小写)
这两个文件名是 make 命令的默认查找对象。当在终端执行 make 时,它会优先先在当前目录下寻找这两个文件,找到后按其中的规则执行构建。
【如果想用其他文件名怎么办?】
如果你的想将构建规则文件命名为其他名称(比如 mybuild),可以通过 make 的 -f 或 --file 参数指定文件名,例如:
make -f mybuild # 告诉make使用mybuild文件作为规则文件
【为什么推荐用 Makefile?】
在实际开发中,更推荐使用 Makefile(首字母大写),原因是:
- 它在目录中会更醒目(通常大写字母的文件会排在前面),方便开发者和其他开发者快速识别项目的构建规则文件。
- 符合大多数开源项目的约定,增强代码的规范性和可读性。
总结:默认情况下,make 只认 Makefile 或 makefile,但通过 -f 参数可以指定任意文件名作为构建规则文件。
【问题】:如果打乱依赖关系的顺序会正常执行吗?
在 Makefile 中,规则的顺序不影响依赖关系的解析,因为 make 是基于 “依赖树” 的拓扑顺序来执行编译的,而非按照规则在文件中的书写顺序。
以提供的 Makefile 为例,即使将规则顺序打乱,比如:
# 汇编文件hello.s,依赖于hello.i
hello.s: hello.igcc -S hello.i -o hello.s# 最终可执行文件hello,依赖于hello.o
hello: hello.ogcc hello.o -o hello# 预处理文件hello.i,依赖于hello.c
hello.i: hello.cgcc -E hello.c -o hello.i# 目标文件hello.o,依赖于hello.s
hello.o: hello.sgcc -c hello.s -o hello.o
make 依然会自动梳理依赖关系的层级:
- 要生成
hello,需要先有hello.o; - 要生成
hello.o,需要先有hello.s; - 要生成
hello.s,需要先有hello.i; - 要生成
hello.i,需要先有hello.c。
最终会按照 hello.c → hello.i → hello.s → hello.o → hello 的正确顺序执行编译命令。
这是因为 make 的核心逻辑是 “先处理依赖项,再处理目标项”—— 它会递归地查找每个目标的依赖,直到找到最底层的源文件(如 hello.c),再从下往上依次执行编译。
因此,只要依赖关系的定义是正确的,规则在 Makefile 中的书写顺序可以任意调整,make 都能正确识别并按依赖层级执行构建。
📝三、make 的工作原理:如何判断 “是否需要编译”?
make 的核心逻辑是比较 “目标文件” 和 “依赖文件” 的 “最近修改时间”:
- 如果 “依赖文件” 的修改时间晚于“目标文件”,说明依赖文件被更新过,需要重新编译生成目标文件。
- 如果 “目标文件” 不存在,也会触发编译。
【stat指令】
语法:
stat [选项] [文件]功能:查看文件的详细元数据(包括 ACM 时间、大小、权限、inode 等)
常用选项:
-c %y [文件]:仅查看文件的内容修改时间(Modify)-c %z [文件]:仅查看文件的属性变更时间(Change)-c %x [文件]:仅查看文件的最近访问时间(Access)- 无选项:查看文件的完整元数据(含所有时间、权限、inode 等)

【示例1】:目标文件最近修改时间比依赖文件最近修改时间新

这个时候不会触发编译,因为test是最新的
【示例2】:目标文件最近修改时间比依赖文件最近修改时间旧

从图中可以看到重新进行编译了
【注意】:我们比较的是Modify时间,也就是文件内容最近修改的时间
🔍四、伪目标与项目清理
在开发中,我们常需要 “清理编译生成的中间文件”,这就需要用到伪目标(用.PHONY修饰)。伪目标的特性是 “总是被执行”,不会受文件修改时间的影响。
以clean规则为例:
.PHONY: clean
clean:rm -f hello.i hello.s hello.o hello
执行make clean时,无论这些文件是否存在,都会执行rm命令清理它们,方便我们重新编译项目。
🚧五、特殊符号
在 Makefile 中,$@ 和 $^ 是自动变量,用于简化命令书写,分别代表不同的含义:
$@:表示当前规则中的目标文件(即规则中:左边的文件名)。$^:表示当前规则中的所有依赖文件(即规则中:右边的所有文件名,去重后的值)。


📋六、依赖关系缺失问题
缺少test.s生成方式:


make后说没有规则可制作目标“test.s”,该目标是“test.o”所需要的,这个时候就会编译失败
🔗七、如何在使用make后不显示依赖方法

在所有依赖方法前加上@符号后,再次make就会发现依赖方式不回显了

📌八、make工作原理总结
make是如何工作的,在默认的方式下,也就是我们只输入make命令。
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“hello”这个文件 ,并把这个文件作为最终的目标文件。
- 如果hello文件不存在,或是hello所依赖的后面的hello.o文件的文件修改时间要比hello这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成hello这个文件。
- 如果hello所依赖的hello.o文件不存在,那么make会在当前文件中找目标为hello.o文件的依赖性,如果找到则再根据那一个规则生成hello.o文件。(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的啦,于是make会生成 hello.o 文件,然后再用 hello.o 文件声明make的终极任务,也就是执行文件hello了。
- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
- make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦
上述就是makefile工具的基础使用,感谢大家的观看!!!

