
欢迎拜访:Madison-No7个人主页
文章主题:自动化构建工具--make/Makefile
隶属专栏:从0到1掌握Linux
写作日期:2025年10月24号
目录
[2.1 make的自动化推导过程:](#2.1 make的自动化推导过程:)
[2.2 项目清理](#2.2 项目清理)
[2.3 .PHONY伪目标:](#2.3 .PHONY伪目标:)
[2.4 源文件的重新编译:](#2.4 源文件的重新编译:)
[2.5 makefile的相关语法:](#2.5 makefile的相关语法:)
[2.5.1 makefile对单文件的编译:](#2.5.1 makefile对单文件的编译:)
[2.5.2 makefile对多文件的编译:](#2.5.2 makefile对多文件的编译:)
前言:
在一个大型的项目中,源文件不计其数,如果我们手动使用gcc/g++去编译源代码,那将会变得十分麻烦,而且容易出错。所以在Linux中有专门的自动化构建工具---make/makefile,来解决多个源文件编译的问题。
一、什么是make和makefile
⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作。make是一个命令行工具,运行时会读取当前目录的 makefile,根据规则自动执行编译、链接等命令.。
📖简单来说, make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。
makefile带来的好处:
"⾃动化编译",⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。
使用make和makefile:
1.创建makefile文件并进行文本编辑:
makefile中的m大小写都可以。

2.定义依赖关系和依赖方法:

3.执行make命令:

依赖关系:
test:test.c #:左边表示的是目标文件,:右边表示目标文件所依赖的文件列表。
依赖关系是指一个事物的存在或运行必须以另一个事物为前提,比如test可执行文件依赖test.c源文件,因为必须通过test.c翻译才能生成可执行文件。
依赖方法:
gcc -o test test.c
依赖方法是指当存在依赖关系时,如何按顺序、按规则处理这些依赖,先确保test.c存在,再执行程序的翻译过程。
💡 依赖关系和依赖方法在生活中的例子:
比如你今天要做一份宫保鸡丁:
- 依赖关系:宫保鸡丁依赖 "炒好的鸡丁""炒好的花生""切好的黄瓜丁";"炒好的鸡丁" 依赖 "切好的鸡丁" 和 "调好的料汁";"切好的鸡丁" 依赖 "完整的鸡胸肉" 和 "菜刀"。这是一条依赖链。
- 依赖方法:先处理最底层依赖(用菜刀把鸡胸肉切成丁),再处理中间依赖(用切好的鸡丁和料汁炒鸡丁),最后处理最终依赖(把炒好的鸡丁、花生、黄瓜丁混合翻炒,做出宫保鸡丁)。
对应的makefile就是:
宫保鸡丁: 炒鸡丁 炒花生 切黄瓜丁
把所有食材混合翻炒出锅
炒鸡丁: 切鸡丁 料汁
用鸡丁和料汁翻炒
切鸡丁: 鸡胸肉 菜刀
用菜刀把鸡胸肉切丁
二、make的工作原理
2.1 make的自动化推导过程:



观察上图可以发现,执行make指令时,实际运行的命令顺序与makefile中的编写顺序相反。这种反向执行机制源于文件间的依赖关系:要生成test需要test.o,但该文件不存在;而makefile中提供了生成test.o的方法,不过这又需要先有test.s文件。同样地,生成test.s又依赖于test.i文件。因此,完整的构建流程必须从test.i开始,依次生成test.s、test.o,最终才能得到test目标文件。这种依赖关系实质上构成了一个栈式结构,决定了命令的执行顺序与编写顺序相反的特性。
🌟注意:
由于是自动化推导,makefile文件中的依赖方法可以是任意顺序,但是不能缺少。还可以这样:


上面只是为例演示make的自动化推导过程,平时我们编译源文件不会这样定义。
2.2 项目清理

我们有时候需要清理文件,如果使用rm +要清理的文件名,如果要清理很多的文件,难道我们每次都要自己手动敲一遍吗?那就太麻烦了,同样可以使用自动化工具make与makefile,帮助我们清理。
同样需要定义依赖关系和依赖方法,清理文件这个操作,不需要依赖任何其他文件,所以:右边的依赖关系什么也没写,直接执行对应的操作,也就是执行依赖方法,依赖方法可以自己指定删除哪些文件。clean是自己命名的,表示清理的意思,大家也可以根据自己的喜好,取其他的名字。有了上面的依赖关系和依赖方法,此时只需要在命令行中执行make clean指令,就可以把自己指定要删除的文件删除。

📖为什么删除时make后面要加clean?
因为make会自顶向下扫描makefile,单独的make指令,会默认执行第一个依赖方法。
2.3 .PHONY伪目标:
.PHONY 用于声明 "伪目标" (Phony Target)。这些目标不是实际的文件,而是代表一组命令(比如清理文件、编译全部、运行程序等)。如果要想让makefile文件中对应的依赖方法总是被执行,可以用.PHONY对相应的标签进行修饰。
比如:
bash
.PHONY:clean
clean:
rm -rf test.i test.s test.o test
📖总是被执行意思:
强制让目标被执行,即使该目录下存在同名文件。如果目录中没有与目标重名的文件 ,即使不加**.PHONY**,make clean 也会每次都执行对应的命令.

test目标文件没有被.PHONY修饰,只能执行一次test对应的gcc命令。

📖为什么不能连续编译源文件:
如下图,当执行完一次make,对源代码编译后,再去执行make,就不会对源代码重新编译。

对于一个源代码,进行一次编译,得到一个可执行文件后,如果没有对源代码做任何修改,再去对源代码进行编译,make会觉得没必要,不会重新编译,另外如果有1000个源文件需要编译,而我们只对其中的几个做了修改,编译器只对修改了的源文件重新编译,而对没改的源文件,编译器不做处理,这也是为了提升编译的效率。而加上.PHONY修饰test,make可以执行无数次,也就是强制make执行编译,即使该目录下存在test可执行文件了,但是这样是没有必要的。
🌟 结论:
一般情况下,对编译操作不加.PHONY进行修饰,而是对清理操作加.PHONY修饰,因为在该目录下当有很多文件时,如果刚好有个clean文件与makefile中的clean目标文件同名了,make会认为 "目标已完成",不会执行 rm命令,导致清理失败,加上.PHONY修饰,就会无视同名。
2.4 源文件的重新编译:
📖make在什么情况下,才会对源文件重新编译呢?(除开加.PHONY修饰)
执行make命令,自动生成一个可执行文件,如果还想对源文件编译就不行了,因为没有对源代码做任何修改,去对源代码进行编译,编译所生成的可执行文件与之前所生成的也一样,make会觉得没必要,不会重新编译。所以只有修改了源文件的内容,make命令所生成的可执行文件才与之前的不一样。
📖那编译器怎么知道源文件的内容修改了呢?
在Linux下,一切皆文件,文件=内容+属性,而与文件相关联的时间有三个,简记为ACM,A(Access)、C(Change)、M(Modify)。而Modify的时间与文件的内容修改有关,Change时间与文件的属性修改有关。也就是说Modify的时间改变了,就说明文件的内容改变了,就可以对源文件重新编译了;

📖查看文件时间:
**stat test:**查看mytest文件的有关时间。

-
Access:常指的是⽂件最近⼀次被访问的时间。
-
Modify: 内容变更,Modify时间更新。
-
Change:属性变更,Change时间更新。
这三个时间是相互关联的,有的操作可能会同时更新多个时间。
⏳ 修改文件的内容后,查看文件的有关时间:

修改文件的内容,这三个时间都会更新,因为修改文件内容,首先要访问该文件,其次修改后,文件的大小(属于文件属性)会发生变化,所以这三个时间都会更新。
⏳ 单纯的修改文件属性,只有Change时间会变化。

⏳ 单纯的访问文件:

在 Linux 的早期版本中,每当文件被访问时,其Access 时间都会立即更新;这些时间信息都存储在计算机的硬盘上,而硬盘都属于外部设备,进行读写操作会比较慢,过高频率的更新一个文件的Access,当整个系统在被多个用户使用的时候,就会有大量的Access更新行为,这些行为都会往硬盘中写数据,这就会导致整个系统的运行速度下降。而当前的 Linux 版本,通过策略优化:仅在两种场景下更新 Access:
- 文件的修改(Modify)时间或状态变更(Change)时间,晚于当前 Access 时,与Access时间间隔还需超过 24 小时;
- 距离上次 Access 更新已超过 24 小时。
📖手动更新文件时间:
- touch test.c:将test.c文件的所有时间更至最新。
- touch -m test.c:将test.c文件的Modify时间更至最新。
- touch -a test.c:将test.c文件的Access时间更至最新。
- touch -c test.c:将test.c文件的Change时间更至最新。

🌟 总结:
如果要对一个已经编译的源文件重新编译,就需要改变源文件的内容,也就是改变源文件的Modify Time,查看源文件的相关时间用stat指令,改变源文件的Modify时间,可以修改源文件的内容,还可以使用touch指令,手动修改,还可以在makefile中对目标文件加.PHONY修饰。
2.5 makefile的相关语法:
2.5.1 makefile对单文件的编译:

执行make后,test因为有.PHONY修饰,所以无论是否存在同名文件,都会执行对应命令。test目标下的echo $(BIN)会将BIN替换为code,执行echo code后输出code。

如果不想把echo $(BIN)命令在make执行的时候回显出来,可以在命令前加@符号。


📖初始版本的makefile写法:

📖特殊变量:
- $@: 代表⽬标⽂件名。
- $^: 代表依赖⽂件列表

更优雅的写法:


-
%.o:%.c : %.c 展开当前⽬录下所有的.c。****%.o: 同时展开同名.o
-
%<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc

我们对多文件编译的时候,有一个习惯,喜欢把.c文件先编译成.o文件,再通过链接生成可执行文件,所以%.o:%.c是为了应对多文件编译引入,$<也是为了对多文件的编译引入,<符号它的指向就像.o < .c,就是把.c文件编译成.o文件。
2.5.2 makefile对多文件的编译:
📖makefile模板:

如果有很多的源文件需要编译,不可能打出所有的.c源文件,makefile高级的地方就体现在makefile有自己的wildcard函数,可以获取当前目录下的所有.c文件。
完。
今天的分享就到这里,感谢各位大佬的关注,大家互相学习,共同进步呀!
