>> >> >> Reference << << << <<<<<<Ref>>>>>>
Make and Makefile
Modified: 2025-12-31 | Author:ljf12825
  1. make:是一个命令行工具它根据一个名为Makefile的脚本文件中的指令,自动地构建和管理项目
  2. Makefile:是一个文本文件,它定义了源文件之间的依赖关系以及构建这些文件的命令

Make是一个构建工具(build tool),最早在Unix上诞生,用来自动化编译和构建项目
它的核心思想是:

Make根据文件的修改时间,自动决定需要重新编译哪些文件,从而避免全量编译

存在意义

想象一个由多个文件组成的C++项目:

没有make,每次修改后都需要手动输入一长串命令来编译

g++ -c main.cpp -o main.o
g++ -c utils.cpp -o utils.o
g++ -c helper.cpp -o helper.o
g++ main.o utils.o helper.o -o myapp

繁琐且低效。如果只修改了helper.cpp,重新编译所有文件会浪费大量时间
有了Makefile,只需要输入makemake工具就会:

  1. 读取Makefile
  2. 检查依赖关系和时间戳
  3. 只重新编译哪些被修改的文件以及依赖于这些文件的目标
  4. 最后链接可执行文件

这极大地提高了开发效率,实现了增量编译
在C/C++项目中,头文件依赖是最大的麻烦:修改了一个头文件,所有包含它的.c都要重新编译
常见的做法是让编译器生成依赖

g++ -MM main.cpp

这会输出类似:

main.o: main.cpp helper.h utils.h

通常会配合Makefile里的-include来自动引入依赖文件(如.d文件),保证依赖关系正确,而不用手写

Make的核心机制与哲学

Makefile语法

target: dependencies
<TAB> command

示例 假设有一个项目:main.chelper.c,最终形成可执行文件hello
Makefile如下

# 最终目标:生成可执行文件 hello
hello: main.o helper.o
    gcc main.o helper.o -o hello

# 目标文件 main.o 依赖于源文件 main.c和头文件 helper.h
main.o: main.c helper.h
    gcc -c main.c -o main.o

# 目标文件 helper.o 依赖于源文件 helper.c和头文件 helper.h
helper.o: helper.c helper.h
    gcc -c helper.c -o helper.o

# 自定义目标:清理编译生成的文件
clean:
    rm -f *.o hello

# 告诉 make: 'clean' 不是一个文件,而是一个动作名称
.PHONY: clean

伪目标(phony targets) .PHONY不只是clean,可以用它组织构建过程

.PHONY: all debug release

all: debug

debug:
    $(MAKE) CFLAGS="-g -Wall"

release:
    $(MAKE) CFLAGS="-O2 -DNDEBUG"

这体现了Make还能作为一个任务调度器,不只是编译器的前端

条件语句

ifeq ($(DEBUG),1)
    CFLAGS += -g
else
    CFLAGS += -O2
endif

这样就可以make DEBUG=1控制编译模式

递归Make
在大型项目里,通常每个子目录都有自己的Makefile,顶层Makefile统一调度

SUBDIRS = src utils tests

all:
    for dir in $(SUBDIRS); do $(MAKE) -C $$dir; done

变量和隐含规则

变量
可以定义变量来保存编译器名称、编译选项等

# 定义变量
CC = gcc
CFLAGS = -Wall -g
TARGET = hello
OBJS = main.o helper.o

# 使用变量 $(VAR_NAME)
$(TARGET): $(OBJS)
    $(CC) $(OBJS) -o $(TARGET)

main.o: main.c helper.h
    $(CC) $(CFLAGS) -c main.c

helper.o: helper.c helper.h
    $(CC) $(CFLAGS) -c helper.c

clean: 
    rm -f $(OBJS) $(TARGET)

.PHONY: clean

自动变量
make提供了一些特殊的“自动变量”,在规则的命令中非常有用

使用自动变量可以进一步简化

CC = gcc
CFLAGS = -Wall -g
TARGET = hello
OBJS = main.o helper.o

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

# 使用模式规则:如何从 .c 文件构建 .o 文件
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    rm -f $(OBJS) $(TARGET)

.PHONY: clean

这里的%.o: %.c是一个模式规则,它告诉make:任何.o文件都依赖于同名的.c文件,并使用下面的命令来构建。这使得Makefile非常简洁,无需为每个.o文件写重复的规则
GNU Make内建了大量的默认规则,比如

%.o : %.c
    $(CC) -c $(CFLAGS) $< -o $@

很多时候,甚至不用写.o的生成规则,Make会自动推导出来。这就是为什么很多简单的Makefile看起来“缺失了一些规则,但依旧能工作

make的使用

在终端中,进入包含Makefile的目录

  1. 最基本的用法 在项目目录下直接运行
make

默认会执行Makefile中的第一个目标
如果第一个目标是all,那就会先编译所有程序

  1. 指定目标
make target_name

例如

make main
make clean

那就会执行mainclean目标对应的命令

  1. 指定Makefile文件 默认情况下,make会去找
make -f MyMakefile
  1. 多线程编译(加速)
make -j
make -j4 # 开 4 个线程
make -j8 # 开 8 个线程

对大型C++项目编译速度提升非常大

  1. 查看执行的命令 有时候想知道make实际执行了哪些命令,可以加
make VERBOSE=1
make -n # 只打印命令,不执行
  1. 强制重新编译 有时候源文件没变,但想强制重新执行
make -B

忽略时间戳,强制执行所有命令

  1. 清理构建 一般Makefile里会有
.PHONY: clean
clean:
    rm -f *.o main

使用:

make clean

删除中间文件,保证下次编译干净

  1. 指定变量 在命令行传递变量覆盖Makefile内定义的
make CC=gcc
make CFLAGS="-02 -Wall"

非常适合调试或切换编译器

  1. 只执行某一步 有时候只想编译某个.o文件
make main.o

它会自动执行生成main.o的规则,不会编译整个项目