Makefile 是 make 工具的配置文件,主要用于定义如何构建和管理项目的编译过程。Makefile 是 C/C++ 编译项目中的重要工具,尤其在多文件、多模块的项目中,为了方便复杂项目的管理,可以通过自动化规则提高编译效率。
这篇文章是 Makefile 的入门教程的介绍,包括其基本语法、常用功能、以及实际使用中的高级技巧。
Makefile 的基本概念
安装 make:
sudo apt-get update
sudo apt-get install make
核心功能
使用方法
运行make命令时,默认会读取当前目录下的Makefile 文件。
make # 执行默认的目标
make <target> # 执行指定的目标
例如:
make clean # 执行 "clean" 目标
Makefile 的基本语法
1. 目标语法
Makefile 的基本语法如下:
target: prerequisites
commands
- target:目标文件或命令名称。例如可执行文件、目标文件或任务名称。
- prerequisites:依赖文件或其他目标。只有依赖文件发生变化时,target 才会被重新生成。
- commands:生成目标的命令(必须以 Tab 开头)。
示例:
hello: hello.o
gcc -o hello hello.o
hello.o: hello.c
gcc -c hello.c
在示例代码中:
如果 hello.c 被修改,则 hello.o 会重新生成。
如果 hello.o 被修改,则 hello 会重新生成。
2. 变量定义
Makefile 支持变量定义,用于简化配置和重复的命令。
示例1:
CC = gcc
CFLAGS = -Wall -g
# 简单赋值 CC :=$(XX) gcc 当前有效 // 立马获取当前XX变量的值
# 递归赋值 CC =$(XX) gcc // 去递归寻找XX变量的最后一个值
# 条件赋值 CC ?= gcc // 如果有CC变量,则该语句无效
# 追加赋值 CC +=$(XX) gcc // 在原来值的基础上,进行追加
# 使用:$(CC) 获取变量的值
示例2:
x := jake
y := $(x) and rose
x := tonly
all :
echo "x=$(x),y=$(y)"
使用变量(示例1)
hello: hello.o
$(CC) $(CFLAGS) -o hello hello.o
输出:(示例2)
x=windx,y=jake and rose
示例
CC = gcc
CFLAGS = -Wall -O2
hello: hello.o
$(CC) $(CFLAGS) -o hello hello.o
hello.o: hello.c
$(CC) $(CFLAGS) -c hello.c
3. 内置变量
Makefile 提供了一些常用的内置变量,可以减少重复工作。
变量 | 功能
| 变量 | 功能 |
|---|---|
| $@ | 当前目标的名称(target)。 |
| $< | 第一个依赖文件的名称(prerequisite)。 |
| $^ | 所有依赖文件的名称。 |
代码片:
CC = gcc
CFLAGS = -Wall -g
hello: hello.o
$(CC) $(CFLAGS) -o $@ $^
在这里:
- $@ 是 hello(目标)。
- $^ 是 hello.o(所有依赖文件)。
4. 通配符和自动化规则
Makefile 支持通配符和模式匹配规则,用于简化多文件项目的编写。
通配符
*.c:匹配所有.c文件。$(wildcard *.c):列出当前目录下所有.c文件。$(patsubst %.c, %.o, $(wildcard *.c)):将所有.c文件替换为对应的.o文件。
自动化规则
使用模式规则可以自动生成目标文件:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
- 表示所有
.o文件都可以由对应的.c文件生成。
代码:
CC = gcc
CFLAGS = -Wall -O0 -g
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
hello: $(OBJ)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
5. 伪目标
伪目标(phony targets)是指不生成实际文件的目标,通常用于清理、测试等任务。
伪目标也是一个目标,该目标不需要生成,每一次去执行该目标,都会执行。
定义伪目标:
.PHONY: clean # 将当前目标设定为伪目标,每次执行该目标都会执行命令
clean:
rm -f *.o hello
- 使用
.PHONY声明可以避免冲突(例如当前目录下存在名为 clean 的文件时)。
6. 自动推导规则
在使用make编译.c源文件时,可以省略编译一个.c文件所使用的命令。这是因为make存在一个默认的规则,能够自动完成对.c文件的编译并生成对应的.o文件。它执行命令“cc -c”来编译.c源文件。对于上边的例子,此默认规则就使用命令“cc -c main.c -o main.o”来创建文件“main.o”。因此对一个目标文件是“N.o”,倚赖文件是“N.c”的规则。可以省略其规则的命令行,使用 make 的默认命令。此默认规则称为make的隐含规则(关于隐含规则可查看我上传至资源中的 《GUN make中文手册》第九章 [ 使用隐含规则 ] 一章,有详细说明。)
我们书写 Makefile 时,对于一个.c文件如果使用 make 的隐含规则,那么它会被自动作为对应.o文件的一个依赖文件(对应是指:文件名除后缀外,其余都相同的两个文件)。因此我们也可以在规则中省略目标的倚赖.c文件。

自动推导规则_1

自动推导规则_2
我们可以看见,当app文件被创建之后,再次执行 make 命令时,提示:make: 'app' is up to date.,此处是 make 的时间戳管理方式,如果目标生成的时间 晚于 依赖,该目标是最新的,那么该目标就不需要再编译重新生成。(如果想详细了解makefile的时间戳管理机制,可以点击查看这篇文章,对 Makefile 的时间戳管理机制有更详细的说明。<
笨猫猫的小茶馆:Makefile 时间戳管理2 赞同 · 0 评论文章
)
项目示例
这是一个多文件 C 项目的示例 Makefile:
项目目录
project/
├── main.c
├── utils.c
├── utils.h
├── Makefile
```
##### Makefile
```bash
# 编译器和编译参数
CC = gcc
CFLAGS = -Wall -g
# 源文件和目标文件
SRC = main.c utils.c
OBJ = $(SRC:.c=.o)
TARGET = my_program
# 默认目标
all: $(TARGET)
# 链接目标文件
$(TARGET): $(OBJ)
$(CC) $(CFLAGS) -o $@ $^
# 编译规则(自动化规则)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理目标
.PHONY: clean
clean:
rm -f $(OBJ) $(TARGET)
运行说明
- 构建项目
make
- 清理项目:
make clean
这么写缺失中间的流程,接下来我结合图片将 makefile 管理项目的实际流程走一遍:
要求:(虽然是简易版用于教学的,但其中的过程和项目管理的思路,即使在大型项目中也是一致的,不同的只是划分的层级结构、架构更为复杂而已。)

- 创建项目目录

include/project.h集合的头文件

jack文件夹下的文件

- 将
jack.c通过编译生成jack.o文件 ——> 然后将jack.o放入obj文件夹下

- 此处的Makefile用于管理
main.c文件,将main.c编译转化为main.o,将其保存在obj文件夹下统一管理

main.o文件生成成功并放在了统一文件夹obj下,接下来,将main.o和jack.o做链接操作生成app文件,因此,我们需要在obj文件夹下创建Makefile文件,用于将所有集合在此处的.0 文件链接生成可执行文件app

- 成功生成了可执行文件
app

- 因为
main :会自动寻址至依赖的第一个文件,结果将提示文件已存在。所以这里我们要将需要生成的文件 main 设为伪目标。
至此,前面的步骤我们共计完成了 4 步:
第一步,我们将 jack 文件夹下的 jack.c 生成了 jack.o ,将其放在 obj 文件夹下;
第二步,我们将 main 文件夹下的 main.c 生成了 main.o,将其放在 obj 文件夹下;
第三步,在 obj 文件夹下,将这里所有集合着的 .o 文件 最终生成了 可执行文件 app。
最后,我们在整个项目目录下,生成一个 Makefile 文件,用于管理整个项目,这样,最终我们只需要 make 一次,便能够得到整体项目的可执行文件,而不需要经过上述如此繁琐的步骤。
make -C 文件夹/[目标] // 去到该文件夹下 去执行该文件夹下的 Makefile 的 第一个目标[目标]
因为第一个目标已存在,所以此处我们需要提前设置mainclean为伪目标。

- 最终生成可执行文件
app

- 优化 Makefile 代码。

- 添加共享变量,在最终的 Makefile 文件中添加共享变量,这样的话,可供同一级目录 和 该目录下的所有子目录中的 Makefile 文件皆可以共享到最上层的 Makefile文件 中的共享变量。
export 共享变量 // 当前同一级目录下的子目录都可以共享该变量

- 这时候进入了一个新人,分配任务给 rose,创建 rose 文件夹并加入这部分模块的代码,



至此,以上为 Makefile 项目管理在开发中的实际应用(简易版)。
高级功能
条件判断
根据条件设置变量或执行不同的规则。
DEBUG = 1
ifeq ($(DEBUG), 1)
CFLAGS += -g
else
CFLAGS += -O2
endif
包含其他 Makefile
使用 include 将多个 Makefile 文件整合在一起。
include common.mk
- 这种方式适合大型项目,将公共配置提取到单独的文件中。
一些常见问题与技巧
如何调试 Makefile?
运行 make 时加上-n或--dry-run参数,可以查看执行的命令而不真正运行:
make -n
如何提高编译效率?
使用 make 的并行编译选项-j,可以同时编译多个目标:
make -j4
- 其中 4 表示最多允许 4 个任务同时执行。
综上。这篇文章仅仅只介绍了 makefile 的简单用法,更多更详细的内容可以下载资源查看 makefile 手册(中文版),Makefile 是一个强大的工具,适合用于管理从简单到复杂的编译流程。
本章主要介绍了:
- 基础语法:理解目标、依赖和命令的结构。
- 变量和内置变量:使用变量提高可读性和灵活性。
- 自动化规则:使用通配符和模式规则减少重复代码。
- 伪目标:定义清理、测试等任务。
- 高级功能:条件判断、多文件组织、并行编译。
通过不断优化 Makefile,可以显著提高项目的管理效率和可维护性,适合运用和管理大型项目。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。