Makefile基础回顾:基本语法、目标与依赖、变量与函数
各位同学,咱们开始上课。今天聊的是Makefile基础,这部分内容看似简单,但我在内存泄漏排查的实战中,发现很多问题根源恰恰是Makefile写得不够严谨。说白了,Makefile就是你的构建脚本,写不好,后面排查问题会多花好几倍时间。
一、Makefile的基本语法
Makefile的规则其实就一句话:目标依赖于前提,然后执行命令。格式长这样:
目标: 依赖文件列表
命令(前面必须是一个Tab,不是空格)
嗯,这里要注意:那个Tab键,我见过太多新手在这上面栽跟头。我自己刚入行时,有一次用空格代替了Tab,结果make报错“missing separator”,排查了半小时才发现是这个问题。你想想看,多冤啊。
二、目标与依赖
目标(target)可以是最终的可执行文件,也可以是一个中间文件,甚至是一个伪目标(phony target)。依赖(prerequisites)就是构建目标前需要先准备好的文件。
举个例子,我在项目中经常这样写:
app: main.o utils.o
gcc -o app main.o utils.o
main.o: main.c
gcc -c main.c
utils.o: utils.c utils.h
gcc -c utils.c
clean:
rm -f *.o app
这里clean就是个伪目标。为什么需要伪目标?因为如果当前目录下恰好有一个叫clean的文件,make会认为这个目标已经是最新的,不会执行清理命令。我建议用.PHONY显式声明:
.PHONY: clean
clean:
rm -f *.o app
三、变量与函数
Makefile里的变量,说白了就是字符串替换。定义变量用=或:=,引用变量用$(变量名)。
=是递归展开,:=是简单展开。有什么区别?我举个例子你就明白了:
# 递归展开
A = $(B)
B = hello
# 此时 $(A) 的值是 hello
# 简单展开
C := $(D)
D = world
# 此时 $(C) 的值是空,因为定义C时D还没定义
我个人习惯用:=,因为它的行为更符合直觉,不容易出现循环引用的问题。曾经有一次,我在一个大型项目中用了=,结果变量互相引用导致make卡死,排查了半天才发现是变量定义顺序的问题。
四、常用内置函数
Makefile内置函数用$(函数名 参数)的形式调用。我挑几个最常用的说说:
| 函数 | 作用 | 示例 |
|---|---|---|
wildcard |
匹配文件 | $(wildcard *.c) 获取所有.c文件 |
patsubst |
模式替换 | $(patsubst %.c,%.o,$(SRC)) 把.c换成.o |
foreach |
循环处理 | $(foreach dir,$(DIRS),$(wildcard $(dir)/*.c)) |
shell |
执行shell命令 | $(shell ls *.c) |
举个例子,我常用的自动获取源文件并生成目标文件的写法:
SRC := $(wildcard *.c)
OBJ := $(patsubst %.c,%.o,$(SRC))
app: $(OBJ)
gcc -o $@ $^
%.o: %.c
gcc -c $< -o $@
这里$@代表目标文件,$^代表所有依赖,$<代表第一个依赖。这些自动变量能省不少事。
五、知识体系结构图
下面这张图,是我梳理的Makefile基础知识体系,你看一眼就能明白各个知识点之间的关系:
这张图把Makefile基础分成了五个模块:基本语法、目标与依赖、变量、内置函数、自动变量。每个模块之间都有联系,比如变量和函数经常配合使用,自动变量则是在规则中简化写法。
好了,这一章的内容就这些。Makefile基础是后面所有内存泄漏排查技巧的基石,你把这些搞扎实了,后面学起来会轻松很多。