Makefile精读 · M01-01 | 什么是 Make:目标、依赖与配方
模块:M01 心智模型与 make 入口
篇号:M01-01 / 41
预计阅读:30 分钟
主线:GNU Make
文章目录
- 本篇目标
- 30 秒速览
- 正文
- 1. Make 是什么:不是编译器,而是「施工队长」
- 2. 最小可运行例子:三元组一眼看清
- 3. 多条规则:依赖链怎么连起来
- 4. 配方:每一行默认是一条 shell 命令
- 5. Make、Makefile、make 命令:三个词别混
- 6. 为何本专栏以 GNU Make 为主线?
- 7. 对照表:本篇相关术语
- 读 Makefile 检查清单
- 练习
- 判断题(对 / 错)
- 改错题
- 动手题
- 下一篇预告
本篇目标
建立 Make 的整体图景:分清「你手敲编译命令」和「让make按规则推导该编什么」分别在干什么;掌握 Makefile 里最基础的三元组——目标(target)、依赖(prerequisite)、配方(recipe)。学完本篇,你打开任意 Makefile,能先认出「谁依赖谁、怎么生成」,并为 M01-02 的「make 如何找到并执行 Makefile」打好概念基础。
30 秒速览
- Make是构建编排工具:根据「文件是否过期」决定要不要重新执行命令。
- Makefile 的核心是规则:
目标: 依赖+ 下方缩进的配方(要执行的 shell 命令)。 - 目标往往是要生成的文件(如
hello、main.o);也可以是逻辑动作名(如clean,后文会讲.PHONY)。 - 依赖是「先于目标存在/更新」的文件或其它目标;任一依赖比目标新,就会重跑配方。
- 配方必须以Tab缩进(不是空格);默认用
/bin/sh执行每一行。 - 本专栏主线是GNU Make;读文件时先找规则,再看变量与函数(M03、M04)。
正文
1. Make 是什么:不是编译器,而是「施工队长」
gcc、clang、ld负责真正编译、链接。Make不替代它们,而是根据一份说明书(Makefile)决定:
- 现在该生成哪些文件?
- 哪些已经是新的、可以跳过?
- 按什么顺序调用哪些命令?
你敲 make → Make 读 Makefile → 比对时间戳 → 只执行过期规则的配方 ↓ gcc / ar / cp …可以把它想成:
| 角色 | 类比 |
|---|---|
| 编译器 / 链接器 | 真正干活的工人(锯木头、上螺丝) |
| Make | 施工队长:看图纸、看哪块墙该重做 |
| Makefile | 图纸:写清「成品 ← 原料 + 工序」 |
目标文件(.o、可执行文件) | 工地上的半成品 / 成品 |
因此:会敲gcc≠ 会读 Makefile。单条编译命令是一步操作;Makefile 是把「多文件、多步骤、增量跳过」组织成可重复构建的规则网。本专栏的目标,正是让你快速读懂这张网,并写出可维护的网。
2. 最小可运行例子:三元组一眼看清
在空目录里准备源文件与 Makefile:
hello.c:
#include<stdio.h>intmain(void){puts("hello, make");return0;}Makefile:
hello: hello.c gcc -o hello hello.c注意第二行前面是Tab,不是空格。
在该目录执行:
$makegcc-ohello hello.c $ ./hello hello,make$makemake:'hello'is up to date.第二次什么都不做:Make 发现目标hello比依赖hello.c还新(或一样新),判定「无需重建」。
这条规则拆开就是:
| 部分 | 本例中的内容 | 含义 |
|---|---|---|
| 目标 | hello | 想得到的文件(或逻辑名) |
| 依赖 | hello.c | 生成目标前必须就绪、且会影响是否过期的输入 |
| 配方 | gcc -o hello hello.c | 如何从依赖得到目标 |
关系写成一句话:
若依赖比目标新(或目标不存在),就执行配方去更新目标。
这就是 Make 的时间戳心智(M06 会展开边界情况)。本篇先牢固记住这一句。
3. 多条规则:依赖链怎么连起来
真实项目很少只有一条规则。常见形态是「可执行文件 ← 若干.o← 若干.c」:
hello: main.o util.o gcc -o hello main.o util.o main.o: main.c gcc -c main.c util.o: util.c gcc -c util.c你执行make(或make hello)时,Make 大致会:
- 发现默认要做的目标是
hello(默认目标规则见 M01-03)。 - 看
hello依赖main.o、util.o——若它们过期或不存在,先去更新它们。 - 更新
main.o时再看它依赖main.c,必要时执行gcc -c main.c。 - 两个
.o就绪后,再执行链接配方生成hello。
画成依赖图:
hello / \ main.o util.o | | main.c util.c读 Makefile 的第一技能:不要从上到下当「脚本顺序」硬背,而是问——
- 我想要的最终目标是谁?
- 它直接依赖谁?
- 那些依赖是否还有自己的规则?
Make 是按依赖图推导,不是简单的「文件第几行先执行」。配方里的命令顺序仍重要,但规则之间由依赖关系决定先后。
4. 配方:每一行默认是一条 shell 命令
要点(本篇够用,细节后续篇展开):
- 缩进必须是 Tab。若误用空格,GNU Make 常报:
*** missing separator. Stop.(M09-02 会精读这类报错)。 - 配方里每一行,默认交给
/bin/sh -c执行(可用变量改 shell,后文再讲)。 - 配方里可以用 Make 的变量与自动变量(如
$@),那是 M03 的主场;本篇先认「这是要跑的命令」。
同一个目标下可以有多行配方:
hello: hello.c gcc -Wall -o hello hello.c @echo "built hello"- 以
@开头的行:执行时不回显该命令本身(只看到命令输出)。 - 以
-开头的行:命令失败时不让 make 立刻失败(慎用;M09 再谈)。
对照「手敲」与「交给 Make」:
| 手敲 | Makefile |
|---|---|
| 每次自己想该编谁 | Make 根据时间戳决定 |
改了一个.c仍可能全量重链 | 设计好依赖后可只重编受影响部分 |
| 命令散落在笔记/聊天记录里 | 规则写进仓库,人人可make |
5. Make、Makefile、make 命令:三个词别混
| 术语 | 含义 |
|---|---|
| Make | 这类构建工具的总称;本专栏指GNU Make行为 |
make | 你在终端里运行的那个程序 |
| Makefile | 规则文本文件(常见名Makefile/makefile,也可用-f指定,见 M01-02) |
| 规则(rule) | 目标: 依赖+ 配方 |
| 目标(target) | 冒号左侧;常为文件名 |
| 依赖 / 先决条件(prerequisite) | 冒号右侧 |
| 配方(recipe) | Tab 缩进的命令行 |
常见误解:
| 误解 | 澄清 |
|---|---|
| 「Makefile 是另一种编程语言」 | 它是声明式规则 + 少量 Make 语法,配方里仍是 shell 命令 |
| 「Make 会替我选编译器」 | 不会;配方里写什么就跑什么(gcc/clang/自定义脚本) |
| 「Makefile 从上到下顺序执行」 | 默认目标及依赖图决定做什么;未依赖到的规则可以永不执行 |
6. 为何本专栏以 GNU Make 为主线?
- Linux 上默认的
make几乎都是GNU Make;开源 C/C++、嵌入式、大量工程构建文档都以它为准。 - 模式规则、函数、
$(shell)、条件指令等「读大型 Makefile 必备」能力,GNU Make 文档最完整。 - 其它 make(如部分 BSD make)语法有差异;本专栏在M10-02只做边界提示,正文不混用。
你在文档或报错里看到gmake,通常就是 GNU Make 的另一种调用名(尤其在部分 BSD 系统上)。
7. 对照表:本篇相关术语
| 术语 | 含义 |
|---|---|
| 目标 | 规则要更新/生成的名字,常为文件 |
| 依赖 | 决定是否过期、以及先构建谁的输入 |
| 配方 | 更新目标时执行的 shell 命令(Tab 开头) |
| 过期(out of date) | 目标不存在,或任一依赖比目标新 |
| up to date | 无需重建 |
| 依赖图 | 目标与依赖连成的有向关系 |
| GNU Make | 本专栏默认的 make 实现 |
读 Makefile 检查清单
打开任意Makefile,先过一遍:
- 能否指出至少一条完整规则的目标 / 依赖 / 配方?
- 默认你想构建的「成品」是哪个目标?它是否出现在某条规则左侧?
- 配方前是否是Tab?(若编辑器显示为空格,要警惕)
- 是否存在「目标依赖
.o,.o再依赖.c」这类链条?试着在纸上画两三层 - 有没有看起来像动作名、不一定对应真实文件的目标(如
clean、install)?先记下来,M01-03 讲.PHONY
练习
判断题(对 / 错)
- Make 本身负责把 C 源码编译成机器码。
- 规则「
hello: hello.c+ 配方」的含义是:只要hello.c比hello新(或hello不存在),就执行配方。 - 配方行用四个空格缩进和用 Tab 缩进,对 GNU Make 完全等价。
- Makefile 里所有规则都会在每次
make时从上到下执行一遍。
- 错。Make 编排何时调用编译器;真正编译的是配方里的
gcc等程序。 - 对。这是时间戳增量构建的基本规则。
- 错。配方必须以 Tab 开头;空格会导致
missing separator一类错误。 - 错。Make 按默认目标(及你指定的目标)沿依赖图决定执行哪些配方;无关规则可不动。
改错题
下面这份 Makefile 想生成hello,但有问题。请指出至少两处,并给出改正版。
hello: hello.c gcc -o hello hello.c(假设当前目录已有hello.c。)
问题示例:
- 配方行用了空格缩进,应改为Tab。
- (视编辑器而定)若文件名不是 Make 默认会找的名字,或路径不对,也会失败——本篇假定文件名为
Makefile且与源文件同目录;找文件规则见 M01-02。
改正版:
hello: hello.c gcc -o hello hello.c(第二行开头为 Tab。)
动手题
- 按本篇第 2 节写出
hello.c与Makefile,执行两次make,观察第二次输出。 - 改一句
hello.c的字符串后再make,确认会重新编译。 - 用手画依赖图:若再增加
util.o/util.c,图应长什么样?
下一篇预告
M01-02:《make 如何找到并执行 Makefile:-f-C与默认文件名》— Make 按什么顺序找GNUmakefile/makefile/Makefile,-f与-C如何改变工作目录与文件选择,以及「在仓库根目录make」时你实际在驱动哪份文件。学完 M01-02,你就能在多目录工程里找对入口。
