
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

🎬 艾莉丝的Linux专栏简介:

文章目录
- [4 ~> 自动化工程构建:make / Makefile](#4 ~> 自动化工程构建:make / Makefile)
-
- [4.1 自动化构建:make / Makefile](#4.1 自动化构建:make / Makefile)
-
- [4.1.1 概念](#4.1.1 概念)
- [4.1.2 熟悉环节:如何使用](#4.1.2 熟悉环节:如何使用)
- [4.1.3 清理项目(看完下转【构建工程 + 清理工程】)](#4.1.3 清理项目(看完下转【构建工程 + 清理工程】))
- [4.1.4 逻辑图:依赖关系和依赖方法](#4.1.4 逻辑图:依赖关系和依赖方法)
- [4.1.5 编译流程解析(从下往上执行)](#4.1.5 编译流程解析(从下往上执行))
- [4.1.6 关键文件关系](#4.1.6 关键文件关系)
- [4.1.7 Makefile语法特征](#4.1.7 Makefile语法特征)
- [4.1.8 Makefile的语法推导过程](#4.1.8 Makefile的语法推导过程)
- [4.2 构建工程 + 清理工程(详解)](#4.2 构建工程 + 清理工程(详解))
-
- [4.2.1 依赖列表可以为空](#4.2.1 依赖列表可以为空)
- [4.2.2 可以有多个依赖关系](#4.2.2 可以有多个依赖关系)
- [4.2.3 make如何知道code.c是否需要被重新编译?](#4.2.3 make如何知道code.c是否需要被重新编译?)
- [4.2.4 什么叫做总是被执行?](#4.2.4 什么叫做总是被执行?)
- [4.3 出现了这样的崩溃是什么意思?](#4.3 出现了这样的崩溃是什么意思?)
-
- [4.3.1 核心问题解析](#4.3.1 核心问题解析)
- [4.3.2 两种可能原因](#4.3.2 两种可能原因)
- [4.3.3 操作选项说明](#4.3.3 操作选项说明)
- [4.3.4 推荐操作步骤](#4.3.4 推荐操作步骤)
- [4.3.5 注意事项](#4.3.5 注意事项)
- [4.4 [M / m]akefile最佳实践和实用语法](#4.4 [M / m]akefile最佳实践和实用语法)
- [4.5 适度扩展语法](#4.5 适度扩展语法)
- 本文代码完整演示
- 博主手记
- 结尾

4 ~> 自动化工程构建:make / Makefile
有人说:"会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力"。
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
4.1 自动化构建:make / Makefile
4.1.1 概念
make:命令------
make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
makefile / Makefile(首字母大不大小写无所谓):一个文件------描述的是如何编译当前工程------
makefile带来的好处就是"自动化编译",一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
总结:make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
4.1.2 熟悉环节:如何使用
bash
[Alice@VM-4-17-centos Ludy]$ vim code.c
[Alice@VM-4-17-centos Ludy]$ ll
total 12
-rw-rw-r-- 1 Alice Alice 277 Nov 20 13:54 code.c
-rw-rw-r-- 1 Alice Alice 166 Nov 20 13:52 Makefile
-rw-rw-r-- 1 Alice Alice 65 Nov 20 13:47 Makefile-bk
[Alice@VM-4-17-centos Ludy]$ make
gcc -E code.c -o code.i
gcc -S code.i -o code.s
gcc -c code.s -o code.o
gcc code.o -o code
code.o -o code
make: execvp: code.o: Permission denied
make: *** [code] Error 127
[Alice@VM-4-17-centos Ludy]$ cat Makefile
code:code.o
gcc code.o -o code
code.o -o code
code.o:code.s
gcc -c code.s -o code.o
code.s:code.i
gcc -S code.i -o code.s
code.i:code.c
gcc -E code.c -o code.i
4.1.3 清理项目(看完下转【构建工程 + 清理工程】)
工程是需要被清理的。
像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令---"makeclean",以此来清除所有的目标文件,以便重编译。
但是一般我们这种clean的目标文件,我们将它设置为伪目标,用。PHONY修饰,伪目标的特性是,总是被执行的。
bash
code:code.c
gcc -o code code.c
.PHONY:clean
clean:
rm -f code
把上面的指令写进Makefile文件,直接用一个指令就能完成清理工作------
bash
[Alice@VM-4-17-centos Ludy]$ make clean
.PHONY:让make忽略源⽂件和可执⾏⽬标⽂件的M时间对⽐。

4.1.4 逻辑图:依赖关系和依赖方法
一张图搞定------

make命令内部维护了一个栈结构,因为栈是先进后出后进先出的,文件还不存在------入栈,找到文件------出栈。

4.1.5 编译流程解析(从下往上执行)
| 阶段 | 命令 | 输入文件 | 输出文件 | 作用说明 |
|---|---|---|---|---|
| 预处理 | gcc -E code.c -o code.i |
code.c | code.i | 处理宏定义、头文件包含等预处理指令 |
| 编译 | gcc -S code.i -o code.s |
code.i | code.s | 将C代码转换为汇编语言 |
| 汇编 | gcc -c code.s -o code.o |
code.s | code.o | 将汇编代码转换为二进制目标文件 |
| 链接 | gcc code.o -o code |
code.o | code | 链接目标文件生成可执行程序 |
4.1.6 关键文件关系
bash
code.c →[预处理]→ code.i →[编译]→ code.s →[汇编]→ code.o →[链接]→ code
.c:源代码文件(人可读)
.i:预处理后文件(保留C语法)
.s:汇编语言文件(机器指令的文本表示)
.o:目标文件(二进制中间产物,不可直接执行)
无后缀的code:最终可执行文件
4.1.7 Makefile语法特征
下图左侧的code: code.o等行为的Makefile规则:
目标: 依赖表示"生成目标文件需要依赖哪些文件" ;例如
code.o: code.s表示编译code.o需要先有code.s文件。

这个流程展示了编译器"一遍编译"的工作原理,即从源代码(.c)到可执行文件(二进制可执行文件)的完整转换链条。需要实际操作时,可以直接执行gcc code.c -o code一步完成,但图片拆分为多步更清晰地展示了内部过程。

4.1.8 Makefile的语法推导过程

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

4.2 构建工程 + 清理工程(详解)
4.2.1 依赖列表可以为空

4.2.2 可以有多个依赖关系

4.2.3 make如何知道code.c是否需要被重新编译?

4.2.4 什么叫做总是被执行?
bash
$ stat XXX
File: 'XXX'
Size: 987 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1321125 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ whb) Gid: ( 1000/ whb)
Access: 2024-10-25 17:05:30.430619002 +0800
Modify: 2024-10-25 17:05:25.940595116 +0800
Change: 2024-10-25 17:05:25.940595116 +0800
⽂件 = 内容 + 属性
Modify: 内容变更,时间更新
Change:属性变更,时间更新
Access:常指的是⽂件最近⼀次被访问的时间。在Linux的早期版本中,每当⽂件被访问时,其
atime都会更新。但这种机制会导致⼤量的IO操作。具体更新原则,不做过多解释。

.PHONY:让make忽略源文件和可执行目标文件的M时间对比。
4.3 出现了这样的崩溃是什么意思?

这是Vim编辑器的交换文件(Swap File) 提示信息,通常在以下情况出现:
4.3.1 核心问题解析
什么是交换文件?
Vim在编辑文件时会自动创建临时交换文件(.swp结尾),用于崩溃时恢复数据。截图中显示的交换文件路径为:
bash
~/.local/share/nvim/swap//%home%Alice%Ludy%Makefile.swp
为什么会出现这个提示?
该交换文件由用户Alice在 11月25日23:46 创建,对应原文件是 ~Alice/Ludy/Makefile
关键矛盾:现在打开的Makefile修改时间是 11月26日15:43(比交换文件更新),系统检测到文件状态不一致
4.3.2 两种可能原因
另一程序正在编辑:可能有其他Vim窗口或用户(Alice)同时打开了该文件,直接编辑可能导致内容冲突
上次编辑崩溃:之前的Vim会话异常退出(如强制关闭、断电),交换文件保留了未保存的修改
4.3.3 操作选项说明
| 选项 | 含义 | 建议场景 |
|---|---|---|
| (O)pen Read-Only | 只读打开,不修改文件 | 仅查看内容,避免冲突 |
| (E)dit anyway | 强制编辑,忽略交换文件 | 确认无人使用且不需要恢复时 |
| ®ecover | 从交换文件恢复数据 | 需要找回上次未保存的修改 |
| (D)elete it | 删除交换文件 | 确认原文件已更新且无恢复需求 |
| (Q)uit / (A)bort | 退出/中止操作 | 暂时不处理该文件 |
4.3.4 推荐操作步骤
先确认是否有其他程序正在编辑 Makefile(如询问Alice);
若文件已手动保存最新版本,直接按 D 删除交换文件,下次打开不再提示;
若需恢复上次崩溃前的内容,按 R 恢复后,记得手动删除交换文件。
4.3.5 注意事项
交换文件是临时文件,正常退出Vim时会自动删除;
若频繁出现此提示,可能是Vim配置或文件权限问题,可检查
~/.local/share/nvim/swap目录权限。
4.4 [M / m]akefile最佳实践和实用语法
如下图所示------

4.5 适度扩展语法
如下图所示------












bash
1 Bin=code.exe
2 #Src=$(shell ls * .c) # 做法1
3 Src=$(wildcard *.c) # 做法2(推荐)
4 Obj=$(Src:.c=.o)
5 Echo=echo
6 cc=gcc
7 Em=rm -f
8 Flags=-s -Wall
9 LD_Flags=-o
10
11 $(Bin):$(Obj)
12 @$(Echo)"我要开始链接了...$(Obj)-> $(Bin)"
13 @$(cc) $(LD_Flags) $@ @^
14 %.o:%.c
15 @$(Echo) "我要开始编译了...$< -> $@"
16 @$(cc) $(Flags) $<
17 .PHONY:clean
18 clean:
19 $(Rm) $(Obj) $(Bin)
20
21 .PHONY:debug
22 debug:
23 @echo "Bin: $(Bin)"
24 @echo "Obj: $(Obj)"
25 @echo "Src: $(Src)"
26
27 #.PHONY:code
28 #code:code.c
29 # gcc code.c -o code
30 #
31 ##.PHONY:clean
32 #clean:
33 # rm -f code


bash
BIN=proc.exe # 定义变量
CC=gcc
#SRC=$(shell ls *.c) # 采⽤shell命令⾏⽅式,获取当前所有.c⽂件名
SRC=$(wildcard *.c) # 或者使⽤ wildcard 函数,获取当前所有.c⽂件名
OBJ=$(SRC:.c=.o) # 将SRC的所有同名.c 替换 成为.o 形成⽬标⽂件列表
LFLAGS=-o # 链接选项
FLAGS=-c # 编译选项
RM=rm -f # 引⼊命令
$(BIN):$(OBJ)
@$(CC) $(LFLAGS) $@ $^ # $@:代表⽬标⽂件名。 $^: 代表依赖⽂件列表
@echo "linking ... $^ to $@"
%.o:%.c # %.c 展开当前⽬录下所有的.c. %.o: 同时展开同名.o
@$(CC) $(FLAGS) $< # %< 对展开的依赖.c⽂件,⼀个⼀个的交给gcc。
@echo "compling ... $< to $@" # @:不回显命令
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN) # $(RM): 替换,⽤变量内容替换它
.PHONY:test
test:
@echo $(SRC)
@echo $(OBJ)

本文代码完整演示
Makefile-backup
bash
1 #code:code.o
2 # gcc code.o -o code
3 #code.o:code.s
4 # gcc -c code.s -o code.o
5 #code.s:code.i
6 # gcc -S code.i -o code.s
7 #code.i:code.c
8 # gcc -E code.c -o code.i
9 #
10 #.PHONY:clean
11 #clean:
12 # rm -f code.i code.s code.o code
13
14 code.exe:code.o
15 gcc code.o -o code.exe
16 code.o:code.c
17 gcc -c code.c
18 .PHONY:clean
19 clean:
20 rm -f code.o code.exe
21
22 #.PHONY:code
23 #code:code.c
24 # gcc code.c -o code
25 #
26 #.PHONY:clean:
27 #clean:
28 # rm -f code
Makefile
bash
1 Bin=code.exe
2 #Src=$(shell ls *.c) # 做法1
3 Src=$(wildcard *.c) # 做法2
4 Obj=$(Src:.c=.o)
5 Echo=echo
6 cc=gcc
7 Rm=rm -f
8 Flags=-c -Wall
9 LD_Flags=-o
10
11 $(Bin):$(Obj)
12 @$(Echo) "我要开始链接了...$(Obj) -> $(Bin)"
13 @$(cc) $(LD_Flags) $@ $^
14 %.o:%.c
15 @$(Echo) "我要开始编译了...$< -> $@"
16 @$(cc) $(Flags) $<
17 .PHONY:clean
18 clean:
19 $(Rm) $(Obj) $(Bin)
20
21 .PHONY:debug
22 debug:
23 @echo "Bin: $(Bin)"
24 @echo "Obj: $(Obj)"
25 @echo "Src: $(Src)"
26
27
28
29 #.PHONY:code
30 #code:code.c
31 # gcc code.c -o code
32 #
33 #.PHONY:clean
34 #clean:
35 # rm -f code
效果展示
bash
[Alice@VM-4-17-centos Ludy]$ make
我要开始编译了...code.c -> code.o
我要开始链接了...code.o -> code.exe
[Alice@VM-4-17-centos Ludy]$ make clean
rm -f code.o code.exe
[Alice@VM-4-17-centos Ludy]$ ll
total 60
-rwxrwxr-x 1 Alice Alice 6264 Nov 26 03:14 a.out
-rwxrwxr-x 1 Alice Alice 8361 Nov 26 17:34 code
-rw-rw-r-- 1 Alice Alice 499 Nov 26 17:35 code.c
-rw-rw-r-- 1 Alice Alice 17168 Nov 25 23:10 code.i
-rw-rw-r-- 1 Alice Alice 711 Nov 25 23:10 code.s
-rw-rw-r-- 1 Alice Alice 524 Nov 26 17:34 Makefile
-rw-rw-r-- 1 Alice Alice 415 Nov 26 14:27 Makefile-backup
-rw-rw-r-- 1 Alice Alice 65 Nov 20 14:33 Makefile-bk
博主手记

结尾
uu们,本文的内容到这里就全部结束了,艾莉丝再次感谢您的阅读!
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
【Linux基础开发工具 (四)】Linux编程基础:深入理解gcc / g++编译与链接原理
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
