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

目录

前言:

一、先搞懂:make 是什么?Makefile 又是什么?

1.1 背景

1.2 make:Linux 下的自动化构建工具

1.3 Makefile:make 的 “操作手册”

二、最佳实践:先见一下,如何使用

2.1 构建项目

2.2 清理项目

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

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

三、ACM时间管理

3.1 重复make,竟然不让我make?

3.2 Access Time(访问时间)

3.3 Modify Time(修改时间)

3.4 Change Time(状态改变时间)

3.5 详解make如何知道code.c是否需要被重新编译

四、makefile最佳实践和实用语法

4.1 $@与$^

4.2 定义变量

4.3 $<和$^

五、总结:为什么一定要学 Makefile?​

前言:
如果你在 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
AI写代码
bash
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开发效率,特别适合需要管理多文件项目的开发者,讲解了大量实用案例和底层原理分析,帮你彻底理解这个工程化开发必备工具
————————————————
版权声明:本文为CSDN博主「Cx330❀」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yhrxh_ymq/article/details/155357221

阅读剩余
THE END
阿里云ECS特惠活动
阿里云ECS服务器 - 限时特惠活动

云服务器爆款直降90%

新客首单¥68起 | 人人可享99元套餐,续费同价 | u2a指定配置低至2.5折1年,立即选购享更多福利!

新客首单¥68起
人人可享99元套餐
弹性计费
7x24小时售后
立即查看活动详情
阿里云ECS服务器特惠活动