Linux 进阶:一文搞懂 make 工具与 Makefile 编写

🔥个人主页:Cx330🌸
❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》
《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔
🌟心向往之行必能至
🎥Cx330🌸的简介:

目录
2.3 理解Makefile/make,编译工作的推导过程,依赖关系和依赖方法
前言:
如果你在 Linux 下编译过 C/C++ 项目,一定遇到过这样的场景:
修改一个文件后,需要手动输入gcc main.c func.c -o app重新编译,文件多了还容易漏写;如果只改了一个小模块,却要重新编译所有文件 —— 既浪费时间又麻烦。
而make工具和Makefile,就是为解决这个问题而生的自动化构建方案。今天我们就从基础到实战,彻底掌握它们。
一、先搞懂:make 是什么?Makefile 又是什么?
1.1 背景
- 会不会写 makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
- 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile 带来的好处就是 ——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如:Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。可见,makefile 都成为了一种在工程方面的编译方法。
- make 是一条命令,makefile 是一个文件,两个搭配使用,完成项目自动化构建。
1.2 make:Linux 下的自动化构建工具
make是一个命令行工具,核心能力是:
- 检查文件的修改时间(对比目标文件与依赖文件)
- 只重新编译 “被修改过的依赖文件” 对应的目标
- 按预定规则(写在 Makefile 里)逐步执行构建步骤
简单说:它能帮你 “智能编译”,避免重复劳动。
1.3 Makefile:make 的 “操作手册”
Makefile 是一个文本文件,没有后缀名,核心作用是定义构建规则—— 告诉 make:
- 最终要生成的目标文件(比如可执行程序
app) - 生成目标需要依赖哪些文件(比如
main.o、func.o) - 具体用什么命令生成目标(比如
gcc -c main.c)
没有 Makefile,make 命令就会报错(不知道该做什么)。
二、最佳实践:先见一下,如何使用
2.1 构建项目
1. 创建Makefile文件
touch Makefile //创建Makefile文件
2. vim编辑Makefile

3. make

2.2 清理项目
如果要清理项目时,可能会不小心将程序搞崩溃,所以这时候就需要Makefile自动化清理


2.3 理解Makefile/make,编译工作的推导过程,依赖关系和依赖方法

我们现在将clean换到前面,我们再make试一试:


我们发现make变成了进行clean的操作,那么我如果想编译code.c呢?—接着往下看:

我们只需在make后面加上目标文件即可!
那么make的逻辑是什么呢?
Makefile、make
默认形成的是从上向下遇到的第一个目标文件!!
且执行一组依赖关系和依赖方法
如图:

思考:make / Makefile:具体是如何形成可执行程序的呢???推导过程又是如何呢?
我们打开vim编辑Makefile

我们make一下:



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


2.4 PHONY:伪目标——总被执行

三、ACM时间管理
3.1 重复make,竟然不让我make?
这里我们重复make发现不让我们make了,这里显示的意思是code文件没有进行更新
为什么我没有更新就不让我make呢?
答:gcc编译代码的时候,代码如果没有被编译过,或者曾经被修改过,make+gcc才会进行重新编译!

那么make如何知道code.c是否需要被重新编译呢?
这里我们补充一个指令:
查看文件时间:stat code.c

3.2 Access Time(访问时间)
- 缩写:atime
- 含义:文件内容被读取 / 访问的时间(仅指查看内容,不包含修改)。
- 触发场景:用
cat/less/more打开文件查看内容、用grep匹配文件内容等。 - 注意:部分系统为优化性能,会通过
noatime挂载选项关闭 atime 的自动更新(避免频繁写磁盘)。
3.3 Modify Time(修改时间)
- 缩写:mtime
- 含义:文件内容被修改并保存的时间(仅与文件内容变更相关)。
- 触发场景:用编辑器(如
vim)修改文件内容后保存、用echo "内容" >> 文件追加内容等。 - 关联:mtime 变更时,文件的元数据也会变化,因此 ctime 会同步更新。
3.4 Change Time(状态改变时间)
- 缩写:ctime
- 含义:文件元数据(非内容)被修改的时间(元数据包括权限、所有者、文件大小、文件名、链接数等)。
- 触发场景:用
chmod修改权限、chown变更所有者、mv重命名文件、rm删除文件(删除前会改元数据)等。
3.5 详解make如何知道code.c是否需要被重新编译
我们再来回顾一下:
文件 = 内容 + 属性
先说结论:文件内容改变影响的是Modify时间,文件属性改变影响的是Change时间
这里我们改变文件属性,来验证哪个时间会变化:

说明:改变文件属性影响的是文件的Change时间,而其他时间不变
我们再来改变文件内容,来验证哪个时间会改变:

这里我们改变code.c的内容来验证一下

从图中发现我们改变内容,ACM时间都改了!!!
在Linux系统中,修改文件内容,文件属性就会因此而变化,这是正常的!!!
我们再来读取一下文件信息,来观察时间变化情况:

先说结论这里读取文件信息会影响的是Access时间
但是你图片上展示的Access、Modify、Change时间都是一样的啊?我怎么知道你改变的是哪个?
说明:
我们读取文件会导致大量IO操作,Linux系统不会傻傻的你用一次就给你更新一下Access时间,这样会导致太大的IO操作,Linux对其进行特殊处理,它有一个策略,就是在你访问7、8次识别出来,他才会更新一下,最后总结一下:Access时间更新最频繁,但是他不是常更新,他有自己的策略,和系统有关
那你还没有给我解决前一个问题啊:make怎么知道我的文件是否需要被重新编译呢?
我们再来make一下:

永远都是先有源文件才有目标可执行文件!所以我们可以来画个图:

code文件时间比源文件时间新,此时我们再make一下看看结果

果然如此,所以我们得到结论:make是如何知道code.c需要被重新编译?
解析:
那么我可执行程序的时间比源文件新,说明源文件没有更新,也就说明Modify时间没有变!,如果源文件比可执行程序文件新,说明我们源文件被改了,Modify时间变了!此时make就知道code.c需要被重新编译了!
答:只要code.c的Modify时间比code可执行程序文件新,就需要重新编译!
那么我们将.PHONY修饰一下gcc编译试一下:

我们再来make一下:

此时我们发现make竟然可以一直执行,那么我想问一下大家知道.PHONY的本质了嘛?
.PHONY的本质:忽略实践对比!
但是我们不建议用.PHONY进行修饰!
四、makefile最佳实践和实用语法
在编译过程中,我们都倾向于将源文件 .c 先编译为 .o 文件

分两步是比较推荐的做法!

4.1 $@与$^
code:code.o gcc -o $@ $^ // $@代表code $^代表code.o code.o:code.c gcc -c code.c .PHONY:clean clean: rm -rf code.o code.exe 4.2 定义变量

我们再来make一下看是否可以执行

我们可以观察到都是可以执行的!

我们再来打印一下要进行的操作!

有没有发现这样很挫,能不能不让它回显:有的!

我们只需要在前面加上@符号即可,我们再来make一下:

这样他就不会回显出来了!
那么如果不想让gcc -c code.c和gcc -o code.exe code.o回显,同样可以在他们前面加上@


查看文件:


4.3 $<和$^

$^ 表示所有的依赖文件
$< 代表第一个依赖文件

如果我们创建了10个文件都进行编译,我们来看一下make结果:

我们创建了10个.c文件,但为什么就给我一个.o编译为.exe?
这是因为我们在声明.o文件时只声明一个,那么向编译10个就要全写上嘛?那如果100个那岂不是很麻烦?

那么接下来有两种方法来进行展开:
做法一:


这时候就可以观察到全部展开了!
我们发现没有.o文件啊,这里有个语法make自动识别:

我们再来make一下

这样.o文件也展开了
我们再对Makefile进一步优化:


这样就可以全部删除了
再进行优化:

如果我们创建上1000个.c文件进行编译,我们来看一下它的编译时间:

这里大家可能看不到过程,其实是编译了很长时间的,我们在make一下发现他不让我百衲衣,因为会花费大量时间,我们再来code.c把源文件修改一下,如何发现再make它编译一下就出来了,这是为什么呢?
因为他只编译了被修改的内容!!!
我们之前说到的展开文件还有一个做法也是比较推荐的:

五、总结:为什么一定要学 Makefile?
- 效率提升:只编译修改过的文件,大型项目节省大量时间;
- 统一流程:团队协作时,所有人用同一个 Makefile,避免 “本地能跑,别人跑不了”;
- 进阶基础:后续学习 CMake(生成 Makefile 的工具)、自动化部署,都需要理解 Makefile 的核心逻辑。
如果你是 Linux 开发新手,建议从 “多文件案例” 开始动手实践,重点注意Tab 键和变量简化这两个细节 —— 上手后会发现,Makefile 其实比想象中简单!
结语:掌握Makefile能显著提升Linux开发效率,特别适合需要管理多文件项目的开发者,讲解了大量实用案例和底层原理分析,帮你彻底理解这个工程化开发必备工具