
🎬 个人主页 :Vect个人主页
🎬 GitHub :Vect的代码仓库
🔥 个人专栏 : 《数据结构与算法》《C++学习之旅》《Linux》
⛺️Per aspera ad astra.
文章目录
Makefile
0. 简单代码演示
cpp
// add.h
#pragma once
int add(int a, int b);
cpp
// add.cpp
#include "add.h"
int add(int a, int b) { return a + b;}
cpp
// main.cpp
#include <iostream>
#include "add.h"
int main(){
std::cout<<add(1,2)<<std::endl;
return 0;
}
1. Makefile结构及规则
这里先提前有个认知:
Makefile是文件make是指令
先来看代码,然后解读:
bash
# 变量定义部分
SRC = main.cpp add.cpp
OBJ = $(SRC:.cpp=.o)
BIN = myapp
# 默认目标
$(BIN): $(OBJ)
g++ -o $@ $^
# 编译目标
%.o: %.cpp
g++ -c $< -o $@
# 清理目标
.PHONY: clean
clean:
rm -f $(OBJ) $(BIN)
变量定义部分
SRC = main.cpp add.cpp
- 作用:定义源文件变量,包含所有
.cpp的源文件 - 解释:
SRC是一个包含需要编译的源文件的列表
OBJ = $(SRC:.cpp=.o)
- 作用:通过模式替换,将
SRC中的.cpp全部转换成.o文件 - 解释:
$(VAR)用来引用一个变量的值,$(SRC:.cpp=.o)利用替换模式 得到OBJ = main.o add.o
BIN = myapp
- 作用:定义最终生成的目标文件名(最后的可执行文件)
默认目标部分
$(BIN):$(OBJ)
- 作用:表示目标
$(BIN)依赖于$(OBJ)中的main.o add.o
g++ -o $@ $^
- 作用:使用
g++将.o文件链接成最终可执行文件myapp - 解释:
$@:代表当前规则中的目标文件,这里是$(BIN)$^:代表所有的依赖文件(去除重复的),这里是$(OBJ)
编译目标部分
%.o: %.cpp
- 作用:表示一条通用规则,告诉
make如何将.cpp文件编译成.o文件 - 解释:
%是通配符,代表一个任意的字符序列,这里表示会匹配所有的.cpp文件,将它们全部编译为对应的.o文件
g++ -c &< -o $@
- 作用:使用
g++将.cpp文件编译成.o文件 - 解释:
$<:代表当前规则中的第一个依赖文件,在这里就是.cpp文件$@:表示当前规则中的目标文件,在这里是.o文件
清理目标部分
.PHONY: clean
- 作用:声明
clean是一个伪目标,而不是文件名 - 解释:
make会认为clean是一个任务,而不是文件,因此即使当前目录下存在名为clean的文件,make也会执行clean规则的命令
clean: rm -f $(OBJ) $(BIN)
- 作用:
clean目标的命令部分,删除所有生成的目标文件和最终的可执行文件。
2. 过程推导
目标文件.o和依赖关系
main.o依赖于main.cppadd.o依赖于add.cpp
在Makefile中,$OBJ 是要生成的目标文件,它的生成依赖于.cpp文件
规则推导过程
make会从默认目标 myapp开始,通过依赖关系逐步推导出需要做的工作
make看到myapp
myapp的形成依赖于main.o和add.o,make会从main.o和add.o开始推导生成
main.o而
main.o的生成又依赖于main.cpp,所以make会执行:g++ -c main.cpp -o main.o生成
add.o同理
add.o的生成依赖于add.cpp,所以make会执行:g++ -c add.cpp -o add.o链接成
myapp当生成了
main.o和add.o,make会执行:g++ -o mayapp main.o add.o
推导是一个出栈入栈的过程
- 出栈:
make开始时看到myapp,出栈myapp,处理它的依赖main.o和add.o- **入栈:**当
main.o和add.o处理完后,它们的目标文件会 入栈 ,然后make执行生成myapp的命令
我们梳理清楚依赖链:
| 目标 | 直接依赖 | 构建规则 |
|---|---|---|
myapp |
main.o add.o |
g++ -o myapp main.o add.o |
main.o |
main.cpp |
g++ -c main.cpp -o main.o |
add.o |
add.cpp |
g++ -c add.cpp -o add.o |
clean |
伪目标 | rm -f main.o add.o myapp |

时间戳
make在做依赖关系推导的时候,会用时间戳来决定哪些目标需要重新构建
时间戳的作用
make会比较每个目标文件和它的依赖文件的时间戳
- 如果依赖文件比目标文件新,
make会重新构建目标文件 - 如果目标文件存在且没有被更新,
make会跳过编译过程
bash
[vect@VM-0-11-centos make_file]$ stat main.cpp
File: 'main.cpp'
Size: 102 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1051720 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1002/ vect) Gid: ( 1002/ vect)
Access: 2025-12-14 20:12:03.883557588 +0800
Modify: 2025-12-14 20:12:01.955499062 +0800
Change: 2025-12-14 20:12:01.955499062 +0800
Birth: -
看一下三种时间:
Acesstime:访问时间,文件内容被读取/访问的时间Modifytime:修改时间,文件时间内容被修改的时间(文件大小、内容变化)Changetime:状态改变时间,文件**元数据(属性)**改变的时间(文件属性)
过程演示:
第一次运行
make
make的行为:
bash[vect@VM-0-11-centos make_file]$ make g++ -c main.cpp -o main.o g++ -c add.cpp -o add.o g++ -o myapp main.o add.o此时目标文件和可执行文件都生成了,时间戳被记录
修改源文件并运行
make假设修改了
add.cpp文件中的代码,例如:
cppint add(int a, int b) { return a * b; // 修改了加法为乘法 }现在,
make会根据文件时间戳决定是否重新编译:
main.o时间戳未变化,main.cpp不重新编译add.o文件的时间戳比add.cpp新,make会发现add.o的依赖文件add.cpp发生变化运行指令得到:
bash[vect@VM-0-11-centos make_file]$ make g++ -c add.cpp -o add.o g++ -o myapp main.o add.o不做任何修改,直接运行
make
bash[vect@VM-0-11-centos make_file]$ make make: `myapp' is up to date.
总结:
-
目标文件不存在 :如果目标文件(
.o)或依赖文件(.cpp)不存在,make会强制编译并生成目标文件。 -
依赖文件更新 :如果依赖文件的时间戳比目标文件更新,
make会重新编译依赖文件并更新目标文件。 -
无更新时跳过编译 :如果目标文件和依赖文件的时间戳都没有变化,
make会跳过编译过程,避免重复工作。

伪目标
伪目标:没有对应文件的目标文件,用来执行命令而不关心文件的存在
伪目标不会检查时间戳,每次执行都会运行相关指令
bash
.PHONY: clean
clean:
rm -f $(OBJ) $(BIN)
.PHONY 告诉 make clean 是伪目标,即使当前目录下有一个 clean 文件,make 也不会认为它是一个文件,而是会执行 rm 命令