工程化

Makefile 多目录项目模板

当项目从单文件增长到多目录、多模块时,手动编译变得不可维护。Makefile 是 C 项目工程化的第一步。本文提供一个可直接套用的多目录项目模板,涵盖自动推导、增量编译和头文件依赖检测。

一、项目目录结构

project/
├── Makefile
├── include/          /* 公共头文件 */
│   ├── utils.h
│   └── config.h
├── src/              /* 源码 */
│   ├── main.c
│   └── utils.c
└── build/            /* 编译产物(自动创建) */
    ├── main.o
    └── utils.o

二、基础 Makefile 模板

# 编译器与标志
CC      := gcc
CFLAGS  := -Wall -Wextra -O2 -Iinclude
LDFLAGS :=
TARGET  := app

# 目录
SRCDIR  := src
INCDIR  := include
OBJDIR  := build

# 源文件与目标文件
SRCS    := $(wildcard $(SRCDIR)/*.c)
OBJS    := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS))

# 默认目标
all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) $^ -o $@

$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
	$(CC) $(CFLAGS) -c $< -o $@

$(OBJDIR):
	mkdir -p $(OBJDIR)

# 清理
clean:
	rm -rf $(OBJDIR) $(TARGET)

# 伪目标声明
.PHONY: all clean
关键变量:$^ 表示所有依赖,$@ 表示目标,$< 表示第一个依赖。竖线 | 后面的顺序依赖只要求存在,不触发重建。

三、自动检测头文件变更

上述模板有一个问题:修改 .h 头文件后,不会重新编译依赖它的 .c 文件。通过 -MMD -MP 生成依赖文件解决:

CFLAGS  := -Wall -Wextra -O2 -Iinclude -MMD -MP

# 自动包含生成的 .d 依赖文件
DEPS := $(OBJS:.o=.d)
-include $(DEPS)

编译时 -MMD 会生成 .d 文件,记录每个 .o 依赖哪些头文件。下次 make 时,如果头文件修改,对应的 .c 会自动重新编译。

三、跨平台兼容写法

不同系统的 rmmkdir 命令有差异,使用条件判断增强兼容性:

ifeq ($(OS),Windows_NT)
    MKDIR   := mkdir
    RM      := del /Q
    TARGET  := app.exe
else
    MKDIR   := mkdir -p
    RM      := rm -rf
    TARGET  := app
endif

四、常用技巧速查

  • make -j4 — 4 线程并行编译,大幅加速大项目
  • make clean && make — 全量重新编译
  • make V=1 — 显示完整编译命令(调试 Makefile 时用)
  • @echo "Compiling $<..." — 静默模式输出自定义信息

五、完整增强版模板

# 可配置变量
CC      ?= gcc
CFLAGS  ?= -Wall -Wextra -O2 -g
TARGET  ?= app

SRCDIR  := src
OBJDIR  := build
INCDIR  := include

SRCS    := $(wildcard $(SRCDIR)/*.c)
OBJS    := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS))
DEPS    := $(OBJS:.o=.d)

CFLAGS  += -I$(INCDIR) -MMD -MP

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) $^ -o $@
	@echo "Build complete: $@"

$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
	$(CC) $(CFLAGS) -c $< -o $@

$(OBJDIR):
	@mkdir -p $@

clean:
	@rm -rf $(OBJDIR) $(TARGET)
	@echo "Cleaned"

rebuild: clean all

.PHONY: all clean rebuild
-include $(DEPS)
使用方式:复制模板到项目根目录,修改 SRCDIR/INCDIR 即可。新增 .c 文件无需修改 Makefile, wildcard 自动识别。