Makefile 快速入门

Posted on 2025-11-28 17:08  0泡  阅读(0)  评论(0)    收藏  举报

Makefile 快速入门

Gaowei基于自己写的一个简单的C++项目,写了一个Makefile,分享下快速入门Makefile的必备结构与整体工作原理。

项目目录结构如下:

├── main.cpp
├── util.cpp
├── Makefile
├── README.md
└── tools

1. 一个最小可用示例

# 变量定义
CC = gcc # C 编译器
CXX = g++ # C++ 编译器
CXXFLAGS = -std=c++11 -Wall -Wextra -g -pthread # 编译参数
TARGET = app # 主程序可执行文件名
SRCS = main.cpp util.cpp # 源文件
OBJS = $(SRCS:.cpp=.o)  # 目标文件,含义是:将 main.cpp 和 util.cpp 编译生成 main.o 和 util.o
OBJ_DIR = obj # 目标文件目录

# 默认目标: 编译主程序,下面含义是:all 目标依赖 app 可执行文件
all: $(TARGET)

# 链接规则: 链接目标文件生成可执行文件
# 下面含义是:app 文件依赖 objs 目录下的所有 .o 文件
# 并使用 gcc 编译器链接生成 app 可执行文件
# 这里`@`表示当前目标(app),`$^`表示所有依赖(main.o 和 util.o)
$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

# 通用编译规则(模式规则): 编译源文件生成目标文件
# 下面含义是:任何 .o 文件都依赖同名 .c 文件
# 并使用 gcc 编译器编译生成 .o 文件
# 这里`$<`表示第一个依赖(main.c 或 util.c),`$@`表示当前目标(main.o 或 util.o)
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# 清理: 清理生成的可执行文件和目标文件
# 下面含义是:清理 app 可执行文件和 objs 目录下的所有 .o 文件
# 这里`-f`表示强制删除
clean:
	rm -f $(TARGET) $(OBJS)

# 伪目标: 告诉 Make “这个名字不是文件”,避免与同名文件冲突
# 下面含义是:all 和 clean 目标不是文件,避免与同名文件冲突
.PHONY: all clean

关键要素说明:

  • 上面命令等价于: g++ -std=c++11 -Wall -Wextra -g -pthread -o app main.cpp util.cpp
  • 变量 (CC, CFLAGS, TARGET 等):避免重复,便于集中调整编译器、标志、文件名。使用语法 $(VAR) 引用。
  • 目标(Targets)all$(TARGET)clean 等。每个目标包含依赖(冒号右侧)和命令(缩进必须是 Tab)。
  • 自动化变量$@ 表示“当前目标”,$^ 表示“所有依赖”,$< 表示“第一个依赖”。能够减少硬编码。
  • 模式规则%.o: %.c 表示“任何 .o 文件都依赖同名 .c”,常用于批量描述编译步骤。
  • 伪目标 .PHONY:告诉 Make “这个名字不是文件”,避免与同名文件冲突。

只要具备以上元素(变量、目标、依赖、命令、必要的伪目标)就能完成一个基础 Makefile。

2. Makefile 的工作流程与原理

Make 的核心理念是“根据时间戳判断目标是否需要重新构建”。整体流程如下:

  1. 解析文件
    Make 读取 Makefile,展开变量和包含语句,建立“依赖图”。

  2. 确定默认目标
    默认是文件中第一个目标(通常写成 all)。也可以通过命令行 make targetName 指定。

  3. 递归检查依赖
    对于目标 T,Make 会逐一检查其依赖 D1, D2, ...。如果依赖本身也有规则,则先尝试构建依赖。

  4. 时间戳比较
    当任一依赖比目标“更新”(即修改时间较晚),或目标不存在时,执行目标的命令。否则跳过,表示“已经是最新”。

  5. 执行命令
    命令行默认在 /bin/sh 中执行,并逐行输出。若某条命令返回非零状态,构建终止。常用前缀:

    • @:抑制命令回显,只输出结果。
    • -:忽略非零退出码(即便失败也继续)。
  6. 并行与增量构建
    由于依赖图可以并行解析,你可以使用 make -j 并行构建多个目标;同时,只有过期的目标才会重新编译,确保增量构建高效。