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 命令)。
  • 目标往往是要生成的文件(如hellomain.o);也可以是逻辑动作名(如clean,后文会讲.PHONY)。
  • 依赖是「先于目标存在/更新」的文件或其它目标;任一依赖比目标新,就会重跑配方。
  • 配方必须以Tab缩进(不是空格);默认用/bin/sh执行每一行。
  • 本专栏主线是GNU Make;读文件时先找规则,再看变量与函数(M03、M04)。

正文

1. Make 是什么:不是编译器,而是「施工队长」

gccclangld负责真正编译、链接。Make不替代它们,而是根据一份说明书(Makefile)决定:

  1. 现在该生成哪些文件?
  2. 哪些已经是新的、可以跳过?
  3. 按什么顺序调用哪些命令?
你敲 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 大致会:

  1. 发现默认要做的目标是hello(默认目标规则见 M01-03)。
  2. hello依赖main.outil.o——若它们过期或不存在,先去更新它们。
  3. 更新main.o时再看它依赖main.c,必要时执行gcc -c main.c
  4. 两个.o就绪后,再执行链接配方生成hello

画成依赖图:

hello / \ main.o util.o | | main.c util.c

读 Makefile 的第一技能:不要从上到下当「脚本顺序」硬背,而是问——

  1. 我想要的最终目标是谁?
  2. 直接依赖谁?
  3. 那些依赖是否还有自己的规则

Make 是按依赖图推导,不是简单的「文件第几行先执行」。配方里的命令顺序仍重要,但规则之间由依赖关系决定先后。


4. 配方:每一行默认是一条 shell 命令

要点(本篇够用,细节后续篇展开):

  1. 缩进必须是 Tab。若误用空格,GNU Make 常报:*** missing separator. Stop.(M09-02 会精读这类报错)。
  2. 配方里每一行,默认交给/bin/sh -c执行(可用变量改 shell,后文再讲)。
  3. 配方里可以用 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 为主线?

  1. Linux 上默认的make几乎都是GNU Make;开源 C/C++、嵌入式、大量工程构建文档都以它为准。
  2. 模式规则、函数、$(shell)、条件指令等「读大型 Makefile 必备」能力,GNU Make 文档最完整。
  3. 其它 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」这类链条?试着在纸上画两三层
  • 有没有看起来像动作名、不一定对应真实文件的目标(如cleaninstall)?先记下来,M01-03 讲.PHONY

练习

判断题(对 / 错)

  1. Make 本身负责把 C 源码编译成机器码。
  2. 规则「hello: hello.c+ 配方」的含义是:只要hello.chello新(或hello不存在),就执行配方。
  3. 配方行用四个空格缩进和用 Tab 缩进,对 GNU Make 完全等价。
  4. Makefile 里所有规则都会在每次make时从上到下执行一遍。
参考答案
  1. 错。Make 编排何时调用编译器;真正编译的是配方里的gcc等程序。
  2. 对。这是时间戳增量构建的基本规则。
  3. 错。配方必须以 Tab 开头;空格会导致missing separator一类错误。
  4. 错。Make 按默认目标(及你指定的目标)沿依赖图决定执行哪些配方;无关规则可不动。

改错题

下面这份 Makefile 想生成hello,但有问题。请指出至少两处,并给出改正版。

hello: hello.c gcc -o hello hello.c

(假设当前目录已有hello.c。)

参考答案

问题示例:

  1. 配方行用了空格缩进,应改为Tab
  2. (视编辑器而定)若文件名不是 Make 默认会找的名字,或路径不对,也会失败——本篇假定文件名为Makefile且与源文件同目录;找文件规则见 M01-02。

改正版:

hello: hello.c gcc -o hello hello.c

(第二行开头为 Tab。)

动手题

  1. 按本篇第 2 节写出hello.cMakefile,执行两次make,观察第二次输出。
  2. 改一句hello.c的字符串后再make,确认会重新编译。
  3. 用手画依赖图:若再增加util.o/util.c,图应长什么样?

下一篇预告

M01-02:《make 如何找到并执行 Makefile:-f-C与默认文件名》— Make 按什么顺序找GNUmakefile/makefile/Makefile-f-C如何改变工作目录与文件选择,以及「在仓库根目录make」时你实际在驱动哪份文件。学完 M01-02,你就能在多目录工程里找对入口。