一. Makefile 核心认知:什么是 Makefile?
1.1 背景介绍
- 能否编写 Makefile,从侧面反映了一个人是否具备完成大型工程的能力。
- 在一个工程中,源文件数量众多,并按类型、功能、模块分别存放在不同目录中。Makefile 定义了一系列规则,用于指定哪些文件需要先编译、哪些需要后编译、哪些需要重新编译,甚至支持更复杂的操作。
- Makefile 的核心优势在于自动化编译 。一旦编写完成,只需执行
make命令,整个工程即可自动完成编译,极大地提高了软件开发效率。 make是一个命令行工具,用于解释 Makefile 中的指令。大多数 IDE 都集成了该工具,例如 Delphi 的make、Visual C++ 的nmake、Linux 下的 GNUmake。因此,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 键开头。
二. 入门案例:最简单的 Makefile
假设项目只有一个源文件hello.c,要生成可执行文件hello,并支持清理编译产物。
2.1 源文件hello.c
bash
#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
[Scy@VM-0-3-centos lesson10]$ make
gcc -o hello hello.c
[Scy@VM-0-3-centos lesson10]$ ll
total 912
-rwxrwxr-x 1 Scy Scy 8360 Nov 20 13:45 hello
-rw-rw-r-- 1 Scy Scy 81 Nov 20 13:44 hello.c
-rw-rw-r-- 1 Scy Scy 76 Nov 20 13:00 hello_copy.c
-rwxrwxr-x 1 Scy Scy 8360 Nov 20 09:23 hello_dynamic
-rwxrwxr-x 1 Scy Scy 861216 Nov 20 09:22 hello_static
-rw-rw-r-- 1 Scy Scy 69 Nov 20 13:44 makefile
-rwxrwxr-x 1 Scy Scy 8424 Nov 19 23:39 soft
-rw-rw-r-- 1 Scy Scy 250 Nov 19 22:27 soft.c
-rw-rw-r-- 1 Scy Scy 90 Nov 19 23:11 soft.cpp
-rwxrwxr-x 1 Scy Scy 8968 Nov 19 23:12 softpp
[Scy@VM-0-3-centos lesson10]$ ./hello
hello Makefile!
[Scy@VM-0-3-centos lesson10]$ make clean
rm -f hello
[Scy@VM-0-3-centos lesson10]$ ll
total 900
-rw-rw-r-- 1 Scy Scy 81 Nov 20 13:44 hello.c
-rw-rw-r-- 1 Scy Scy 76 Nov 20 13:00 hello_copy.c
-rwxrwxr-x 1 Scy Scy 8360 Nov 20 09:23 hello_dynamic
-rwxrwxr-x 1 Scy Scy 861216 Nov 20 09:22 hello_static
-rw-rw-r-- 1 Scy Scy 69 Nov 20 13:44 makefile
-rwxrwxr-x 1 Scy Scy 8424 Nov 19 23:39 soft
-rw-rw-r-- 1 Scy Scy 250 Nov 19 22:27 soft.c
-rw-rw-r-- 1 Scy Scy 90 Nov 19 23:11 soft.cpp
-rwxrwxr-x 1 Scy Scy 8968 Nov 19 23:12 softpp

核心用法:
- 编译项目: 在 Makefile 所在目录执行
make,自动查找第一个目标(code),检查依赖是否更新,执行编译命令; - 清理项目: 执行
make clean,执行clean目标下的命令,删除可执行文件; - 增量编译: 修改
code.c后再次执行make,只会重新编译修改后的文件,而非全部重编。
三. Makefile 语法规则:深入理解依赖与命令
3.1 基本语法格式
bash
目标(target): 依赖(prerequisites)
命令1
命令2
...
- 目标可以是可执行文件、目标文件(
.o)、伪目标(如clean); - 依赖可以是源文件、目标文件、其他目标;
- 命令必须以Tab 键 开头(不能用空格,否则
make会报错); - 注释以
#开头,单行有效。
3.2 依赖关系与推导
参考图示 :(Makefile 会自动推导目标文件的依赖)

但是我们一般是用不到这么多步的,最佳实践如下:
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时间对比
图示理解 :(什么叫总是被执行?)

四. 进阶用法:变量与函数(工程化必备)
当项目源文件增多时,手动写每个文件的依赖和命令会很繁琐。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)wildcard:获取指定模式的文件
bash
# 获取当前目录下所有.c文件,存入SRC变量
SRC = $(wildcard *.c)
(2)替换文件后缀
bash
# 将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=code.exe # 定义变量
#Src=$(shell ls *.c) # 做法1 -- 采用shell命令行方式,获取当前所有.c文件名
Src=$(wildcard *.c) # 做法2 -- 使用 wildcard 函数,获取当前所有.c文件名
Obj=$(Src:.c=.o) # 将Src的所有同名 .c 替换成 .o 形成目标文件列表
Echo=echo
cc=gcc
Rm=rm -f
Flags=-c -Wall # 编译选项
LD_Flags=-o # 链接选项
$(Bin):$(Obj)
@$(Echo) "我要开始链接了...$(Obj) -> $(Bin)" # $@: 代表目标文件名 $^: 代表依赖文件列表
@$(cc) $(LD_Flags) $@ $^
%.o:%.c # %.c: 展开当前目录下的所有.c %.o: 同时展开同名.o
@$(Echo) "我要开始编译了...$< -> $@" # @: 不回显命令
@$(cc) $(Flags) $< # %<: 对展开的依赖.c文件,一个个的交给gcc
.PHONY:clean
clean:
$(Rm) $(Obj) $(Bin) # $(RM): 替换,用变量内容替换它
.PHONY:debug
debug:
@$(Echo) "Bin: $(Bin)"
@$(Echo) "Obj: $(Obj)"
@$(Echo) "Src: $(Src)"
~
