
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux操作系统从入门到实践》《Qt从入门到实践》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
[一、Makefile 核心认知:什么是 Makefile?](#一、Makefile 核心认知:什么是 Makefile?)
[1.1 背景介绍](#1.1 背景介绍)
[1.2 核心作用](#1.2 核心作用)
[1.3 核心概念](#1.3 核心概念)
[二、make 基本使用:实现最简单的 Makefile](#二、make 基本使用:实现最简单的 Makefile)
[2.1 源文件code.c](#2.1 源文件code.c)
[2.2 基础 Makefile](#2.2 基础 Makefile)
[2.3 实际操作演示](#2.3 实际操作演示)
[三、Makefile 语法规则:深入理解依赖与命令](#三、Makefile 语法规则:深入理解依赖与命令)
[3.1 基本语法格式](#3.1 基本语法格式)
[3.2 依赖关系与推导](#3.2 依赖关系与推导)
[3.3 伪目标(.PHONY)](#3.3 伪目标(.PHONY))
[4.1 变量定义与使用](#4.1 变量定义与使用)
[4.2 常用函数(简化文件列表)](#4.2 常用函数(简化文件列表))
[4.3 自动变量(简化命令书写)](#4.3 自动变量(简化命令书写))
[4.4 简化 Makefile/makefile(多源文件适配)](#4.4 简化 Makefile/makefile(多源文件适配))
前言
在 Linux C/C++ 开发过程中,随着项目源文件不断增多,手动输入 gcc 命令进行编译会变得异常繁琐且极易出错。我们不仅需要理清各个文件之间的依赖关系,还要严格控制编译顺序与编译参数,效率非常低下。Makefile 的核心价值正是自动化构建:只需编写一次构建规则,后续仅需执行 make 命令,即可自动完成编译、链接、清理等整套流程,大幅提升开发效率与项目可维护性。本文将从 Makefile 的基本原理讲起,逐步拆解语法规则、依赖管理、伪目标、变量与函数等核心知识点,并通过工程化实战案例带你完整上手,让你从 "手动编译" 快速过渡到 "自动化构建"
一、Makefile 核心认知:什么是 Makefile?
1.1 背景介绍
- 会不会写makefile,从侧面说明了一个人是否具备完成大型工程的能力
- 一个工程中的源文件不计其数,其按类型,功能,模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于更复杂的操作
- makefile 带来的好处就是 -- "自动化编译",一旦写好,只需要一个 make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi 的 make,Visual C++ 的 namke,Linux下GNU的make。可见,makefile 都成为了一种在工厂方面的编译方法。
- make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
1.2 核心作用
- 定义源文件之间的依赖关系(如可执行文件依赖目标文件,目标文件依赖源文件);
- 指定编译规则(如如何将.c文件编译为.o文件,如何链接为可执行文件);
- 支持增量编译(只重新编译修改过的文件,减少重复工作);
- 提供项目清理功能(一键删除编译产物,方便重新构建)。
1.3 核心概念
- make:解释 Makefile 规则的命令工具,默认查找当前目录下名为Makefile或makefile的文件;
- 目标(target):要生成的文件或要执行的命令(如可执行文件、清理操作);
- 依赖(prerequisites):生成目标所需要的文件或条件(如生成myproc依赖myproc.c);
- 命令(command):生成目标的具体操作(如gcc -o myproc myproc.c),必须以 Tab 键开头。
二、make 基本使用:实现最简单的 Makefile
假设项目只有一个源文件 code.c,要生成可执行文件 code,并支持清理编译产物。
2.1 源文件code.c
cpp
#include<stdio.h>
int main()
{
printf("hello Makefile!");
return 0;
}
2.2 基础 Makefile

bash
# 目标:依赖
code: code.c
# 命令(必须Tab开头):将code.c编译为可执行文件code
gcc -o code code.c
# 伪目标:清理编译产物
.PHONY: clean
clean:
# -f表示强制删除,忽略不存在的文件
rm -f code
2.3 实际操作演示
bash
[admin@iZbp12ear9ufvimc78fddkZ mk]$ make
gcc code.c -o code
[admin@iZbp12ear9ufvimc78fddkZ mk]$ ll
total 44
-rwxrwxr-x 1 admin admin 8360 May 19 20:17 code
-rw-rw-r-- 1 admin admin 76 May 19 20:17 code.c
-rw-rw-r-- 1 admin admin 1750 May 19 20:15 makefile
[admin@iZbp12ear9ufvimc78fddkZ mk]$ ./code
hello Makefile!
[admin@iZbp12ear9ufvimc78fddkZ mk]$ make clean
rm -f code
[admin@iZbp12ear9ufvimc78fddkZ mk]$ ll
total 32
-rw-rw-r-- 1 admin admin 76 May 19 20:17 code.c
-rw-rw-r-- 1 admin admin 1750 May 19 20:15 makefile

核心用法:
- 编译项目: 在Makefile 所在目录执行make,自动查找第一个目标(code),检查依赖是否更新,执行编译命令;
- 清理项目: 执行make clean,执行 clean 目标下的命令,删除可执行文件;
- 增量编译: 修改 code.c 后再次执行 make,只会重新编译修改后的文件,而非全部重编。
三、Makefile 语法规则:深入理解依赖与命令
3.1 基本语法格式
bash
目标(target): 依赖(prerequisites)
命令1
命令2
...
- 目标可以是可执行文件、目标文件(.o)、伪目标(如clean);
- 依赖可以是源文件、目标文件、其他目标;
- 命令必须以Tab 键 开头(不能用空格,否则make会报错);
- 注释以#开头,单行有效。
3.2 依赖关系与推导
make命令内部维护了一个栈结构,因为栈是先进后出后进先出的,文件还不存在------入栈,找到文件------出栈。
参考图示:(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
编译流程解析(从下往上执行):
| 阶段 | 命令 | 输入文件 | 输出文件 | 作用说明 |
|---|---|---|---|---|
| 预处理 | 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 | 链接目标文件生成可执行程序 |
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 根本不理。
但是我们一般是用不到这么多步的,最佳实践如下:
bash
# 最终目标:可执行文件code.exe,依赖code.o
code.exe: code.o
gcc code.o -o code.exe
# 目标文件code.o,依赖code.c
code.o: code.c
gcc -c code.c -o code.o
# 伪目标clean
.PHONY: clean
clean:
rm -f code.exe code.o
3.3 伪目标(.PHONY)
- 伪目标不是实际文件 ,而是一个**"命令标签"**(如clean);
- 作用:避免项目中存在与伪目标同名的文件,导致 make 误判为 "目标已存在,无需执行";
- 语法 :.PHONY: 伪目标名 ,例如 .PHONY: clean,确保 make clean总是执行命令。
图示理解 :(什么叫总是被执行?)


结论:.PHONY:让make忽略源文件和可执行目标文件的M时间对比
出现了这样的崩溃是什么意思(补充内容)?

这是Vim编辑器的交换文件(Swap File) 提示信息
什么是交换文件?
Vim在编辑文件时会自动创建临时交换文件(.swp结尾),用于崩溃时恢复数据。截图中显示的交换文件路径为:
bash
~/.local/share/nvim/swap//%home%Alice%Ludy%Makefile.swp
为什么会出现这个提示?
该交换文件由用户在 11月25日23:46 创建,对应原文件是 ~Alice/Ludy/Makefile
关键矛盾: 现在打开的 Makefile 修改时间是 11月26日15:43(比交换文件更新),系统检测到文件状态不一致
两种可能原因:
(1)另一程序正在编辑 :可能有其他Vim窗口或其他用户同时打开了该文件,直接编辑可能导致内容冲突;
(2)上次编辑崩溃 :之前的Vim会话异常退出(如强制关闭、断电),交换文件保留了未保存的修改
操作选项说明:
| 选项 | 含义 | 建议场景 |
|---|---|---|
| (O)pen Read-Only | 只读打开,不修改文件 | 仅查看内容,避免冲突 |
| (E)dit anyway | 强制编辑,忽略交换文件 | 确认无人使用且不需要恢复时 |
| (R)ecover | 从交换文件恢复数据 | 需要找回上次未保存的修改 |
| (D)elete it | 删除交换文件 | 确认原文件已更新且无恢复需求 |
| (Q)uit / (A)bort | 退出 / 中止操作 | 暂时不处理该文件 |
推荐操作步骤:
先确认是否有其他程序正在编辑 Makefile(如询问用户);
若文件已手动保存最新版本,直接按 D 删除交换文件,下次打开不再提示;
若需恢复上次崩溃前的内容,按 R 恢复后,记得手动删除交换文件。
注意事项:
交换文件是临时文件,正常退出Vim时会自动删除;
若频繁出现此提示,可能是Vim配置或文件权限问题,可检查 ~/.local/share/nvim/swap 目录权限。
四、进阶用法:变量与函数(工程化必备)
当项目源文件增多时,手动写每个文件的依赖和命令会很繁琐。Makefile 支持变量和函数,可简化配置、提高复用性。
4.1 变量定义与使用
变量用于存储重复出现的内容(如编译器 、编译参数、文件列表)
语法:变量名=值,使用时 $(变量名)。
示例:
bash
# 定义变量:编译器、可执行文件名、目标文件列表、清理命令
CC = gcc # 编译器
BIN = myproc # 可执行文件名
OBJ = myproc.o # 目标文件列表
RM = rm -f # 清理命令
CFLAGS = -Wall -g # 编译参数(-Wall显示所有警告,-g生成调试信息)
# 最终目标:依赖OBJ变量
$(BIN): $(OBJ)
$(CC) $(CFLAGS) -o $(BIN) $(OBJ)
# 目标文件依赖源文件(可省略,make自动推导)
myproc.o: myproc.c
# 清理伪目标
.PHONY: clean
clean:
$(RM) $(BIN) $(OBJ)
4.2 常用函数(简化文件列表)
Makefile 提供内置函数 ,可自动获取文件列表,无需手动罗列。
(1)shell命令行/wildcard函数:获取指定模式的文件
cpp
# 获取当前目录下所有.c文件,存入SRC变量
SRC = $(shell ls *.c)
SRC = $(wildcard *.c)
(2)替换文件后缀
cpp
# 将SRC中的.c文件替换为.o文件,存入OBJ变量
Obj = $(SRC:.c=.o)
4.3 自动变量(简化命令书写)
Makefile 提供自动变量,替代命令中重复出现的目标和依赖,简化书写:
| 自动变量 | 含义 | 示例 |
|---|---|---|
| $@ | 当前目标文件名 | (CC) -o @ $^ |
| $^ | 所有不重复的依赖文件 | (CC) -o @ $^ |
| $< | 第一个依赖文件名 | (CC) -c < -o $@ |
4.4 简化 Makefile/makefile(多源文件适配)
假设项目有多个源文件,Makefile 可自动适配,简化后的 Makefile 如下所示:
bash
BIN=myproc.exe # 定义变量
CC=gcc
#OBJ=code.o
OBJ=$(SRC:.c=.o)
#SRC=code.c
SRC=$(shell ls *.c) # 采用shell命令行方式,获取当前所有.c文件名
#或者SRC=$(wildcard *.c) # 或者使用wildcard 函数,获取当前所有.c文件名
LFLAGS=-o # 链接选项
FLAGS=-c # 编译选项
RM=rm -f
$(BIN):$(OBJ)
@$(CC) $^ $(LFLAGS) $@ # $@:代表目标文件名。 $^: 代表依赖文件列表
@echo "linking $^ to $@" # @:不回显命令
%.o:%.c # %.c 展开当前目录下所有的.c。 %.o: 同时展开同名.o
@$(CC) $(FLAGS) $< # $<: 对展开的依赖.c文件,一个一个的交给$(CC) (gcc)
@echo "compling $< to $@"
.PHONY:
clean:
@$(RM) $(OBJ) $(BIN)
@echo "remove $(OBJ) $(BIN)"
.PHONY:
test:
@echo $(SRC)
@echo $(OBJ)
结束语
本文系统讲解了 Makefile 从基础认知、简单实现,到语法规则、伪目标、变量函数、自动变量等进阶工程化用法,完整梳理了依赖推导逻辑与多文件项目的构建思路,帮大家摆脱手动输入 gcc 编译命令的繁琐。Makefile 是 Linux C/C++ 开发中实现自动化构建的核心工具,熟练掌握它能极大提升项目编译、维护与迭代效率。希望对大家学习Linux能有所收获!