Makefile
自定义变量就是程序员自己编写代码定义的变量OUT := main #目标文件OBJS := main.o add.o #生成目标文件所需要的依赖COM_OP := -Wall -g #编译选项clean:预定义变量,即由Makefile自身预先定义好的变量,我们可以直接拿来,也可以先重新赋值再用。
makefile是什么?
Makefile本质上是一脚本文件,这个文件中存放了编译和构建程序的脚本指令,定义了整个工程构建可执行程序的规则。
Makefile 的基本语法
一个简单的 Makefile 由一系列「规则」组成,每条规则的格式如下:
目标(target) : 依赖(prerequisites)
命令(commands)
- 目标(target):要生成的文件(如可执行文件
main、目标文件main.o),也可以是「伪目标」(如clean,用于执行清理操作)。 - 依赖(prerequisites):生成目标所需的文件或其他目标(如源文件
main.c、func.c)。 - 命令(commands):生成目标的具体操作(如编译命令),必须以 Tab 键开头(不能用空格)。
如果项目有多个源文件(如 main.c、add.c、add.h),需要定义更清晰的依赖关系:
项目结构:
project/
├── main.c
├── add.c
├── add.h
└── Makefile
Makefile:
main: main.o add.o
gcc main.o add.o -o main
main.o: main.c compute.h
gcc -c main.c -Wall -g
add.o: add.c compute.h
gcc -c add.c -Wall -g
clean:
rm -f main main.o add.o #删除所有生成的目标文件以及可执行程序,并且不给提示。-f是必须的
rebuild: clean main #clean和main要写在依赖的位置,而不是命令的位置!
Makefile变量
Makefile变量,在本质上就是一个文本字符串,在执行脚本代码的时候会原模原样地展开在所使用的地方(类似于宏的文本替换)。
Makefile变量在使用的时候:
- 需要给在变量名前加上$符号
- 如果变量名不是单字符,就必须用()或大括号 {} 把变量名括起来。如果变量名是单字符,则允许不用括号。
OUT := main
$(OUT): main.o add.o #脚本执行时,等价于main: main.o add.o 变量名必须用()括起来
Makefile当中的变量,又可以分为三类:
- 自定义变量
- 自动变量
- 预定义变量
自定义变量
自定义变量就是程序员自己编写代码定义的变量
OUT := main #目标文件
OBJS := main.o add.o #生成目标文件所需要的依赖
COM_OP := -Wall -g #编译选项
$(OUT):$(OBJS)
gcc main.o add.o -o main
main.o: main.c compute.h
gcc -c main.c -o main.o $(COM_OP)
add.o: add.c compute.h
gcc -c add.c -o add.o $(COM_OP)
clean:
rm -f $(OUT) $(OBJS)
rebuild: clean $(OUT)
自动变量
Makefile中的自动变量是指那些在规则执行时自动设置的变量,它们通常用于引用规则的目标和依赖项。这些变量在Makefile的执行过程中非常有用,因为它们能够让规则更加简洁且易于管理。下面是一些常用的自动变量,变量名及其描述如下(注意是变量名):
@:表示此规则的目标文件名。<:表示此规则的第一个依赖文件名。^:表示此所有的依赖文件列表,这些依赖项之间以空格分隔。
注意:这些都只是变量名,要使用这些变量的话,需要在变量名前加字符$(单字符名不用加括号)。
于是我们就可以进一步的修改上面的Makefile脚本代码,如下:
OUT := main #目标文件
OBJS := main.o add.o #生成目标文件所需要的依赖
COM_OP := -Wall -g #编译选项
$(OUT):$(OBJS)
gcc $^ -o $@
main.o: main.c compute.h
gcc -c $< -o $@ $(COM_OP)
add.o: add.c compute.h
gcc -c $< -o $@ $(COM_OP)
.PHONY: clean rebuild
clean:
rm -f $(OUT) $(OBJS)
rebuild: clean $(OUT)
预定义变量
预定义变量,即由Makefile自身预先定义好的变量,我们可以直接拿来,也可以先重新赋值再用。
常用的预定义变量如下:

模式规则
通过Makefile的变量,你已经将脚本代码写得很简洁了,但简洁不是我们的主要目的,通用才是。目前这个Makefile,我们觉得它仍然不够通用,于是我们继续学习Makefile的模式规则,以将Makefile写得更加通用。
所谓模式规则:
用于定义如何从一种类型的文件生成另一种类型的文件,它直接指定了文件之间的转换规则。模式规则下一般使用百分号(%)来表示文件名中的通配符部分。
它的语法结构如下:
%.target : %.source
commands
解释如下:
%.target表示可以匹配任意目标文件。比如%.o表示任意.o文件作为目标%.source表示可以匹配任意依赖文件。比如%.c表示任意.c文件作为依赖
使用这个模式规则后,Makefile可以自动推导如何从任何 .c 文件生成 .o 文件,这样就可以大幅度简化脚本代码。
于是有了模式规则后,我们可以这样写 Makefile:
OUT := main #目标文件
OBJS := main.o add.o sub.o #生成目标文件所需要的依赖
COM_OP := -Wall -g #编译选项
CC := gcc #修改CC的默认值
$(OUT):$(OBJS)
$(CC) $^ -o $@
%.o : %.c compute.h
$(CC) -c $< -o $@ $(COM_OP)
#以下内容可以被上面一个模式规则替代
#main.o: main.c compute.h
# $(CC) -c $< -o $@ $(COM_OP)
#add.o: add.c compute.h
# $(CC) -c $< -o $@ $(COM_OP)
#sub.o: sub.c compute.h
# $(CC) -c $< -o $@ $(COM_OP)
.PHONY: clean rebuild
clean:
$(RM) $(OUT) $(OBJS)
rebuild: clean $(OUT)
显然使用模式规则,可以极大的简化代码,而且还可以提高扩展性。
比如这时,我们再增加一个运算mul(乘法),创建一个mul.c文件包含头文件compute,并实现函数。此时我们该如何改这个Makefile呢?
很简单只需要修改一个OBJS自定义变量的值就可以了:
OBJS := main.o add.o sub.o mul.o #生成目标文件所需要的依赖
只需要改变上面一行就可以了。
注意:
- 模式规则不能作为Makefile的第一个规则,因为Makefile会选择第一个明确的目标作为默认目标,而模式规则中的目标,不指定具体的目标文件名,而仅仅定义了从一种文件类型转换到另一种文件类型的通用规则。模式规则中的目标不是一个明确的目标,Makefile脚本执行时若把模式规则写在最上面,脚本执行会跳过这个模式规则目标。
- 模式规则定义了如何将一种类型的文件(比如.c源文件)转换成另一种类型的文件(比如.o目标文件),这种转换是根据文件名模式来完成匹配的。
- 在模式规则中,通配符
%的匹配是一致的,即目标和依赖列表中%所表示的内容是一致的。
内置函数
经过上面的一番折腾,我们的Makefile已经非常简单,非常具有扩展性了。但"不满足"是进步的阶梯,我们能不能把Makefile扩展到新增.c文件完全不需要改动这个Makefile呢?
于是我们就需要学习一下Makefile的内置函数语法。
Makefile脚本语言,也像传统编程语言一样,支持函数调用,这种函数,我们称之为Makefile的"内置函数"。
内置函数的调用语法,和使用变量的语法非常类似,如下:
$(<function> <arguments>) #调用时要用小括号把函数名和参数括起来
${<function> <arguments>} #也可以用大括号括起来,两种方式都可以,使用时二选一
以上两种方式都可以,解释如下:
- <function>为函数名。注意尖括号只是为了语法格式美观,它不是实际调用语法的一部分。
- <arguments>为参数列表,允许出现一个或多个参数,参数之间以逗号分隔。注意尖括号只是为了语法格式美观,它不是实际调用语法的一部分。
- 函数名和参数列表之间以"空格"分隔。
- 参数允许有多个,多个参数之间用","分隔。
Makefile 内置的函数并不算多,我们这里只介绍两个最常用的:
通配符函数
$(wildcard <pattern>)
函数的名字是:wildcard
它的作用是:查找符合<pattern>的所有文件列表
函数的返回值是:返回所有符合<pattern>的文件名,文件名之间以空格分隔。
示例:
SRCS := $(wildcard *.c)
其效果是:查找当前目录下,所有以.c结尾的文件名,并将文件名以空格分隔,再赋值给变量SRCS。
比如当前目录下有.c文件:main.c、add.c、sub.c
那么这个函数调用的作用等价于:
SRCS := main.c add.c sub.c
模式替换函数
$(patsubst <pattern>,<replacement>,<text>)
函数的名字是:patsubst,它是词组"pattern substitution"的简写,意为模式替换。
它的作用是:查找<text>中符合模式<pattern>的单词(单词以空白字符分隔),将其替换为<replacement>。
注意事项:
- <pattern>可以包括通配符%,表示任意长度的字符串。
- 如果<replacement>中也含有%,那么<replacement>中的%所代表的字符串和<pattern>中%所代表的字符串相同。
- <text>作为替换的数据源,它是最后一个参数。
函数的返回值是:返回替换后的字符串
示例:
OBJS := $(patsubst %.c, %.o, foo.c bar.c)
其效果是:将字符串 foo.c、bar.c 中符合模式 %.c 的单词替换成 %.o,返回结果为 foo.o 和 bar.o
注意:该函数的参数部分是以空格为间隔的,只需要一个空格作为间隔符,不要添加过多的空格!!比如下图中的用法就是错误的:
也就是说,相当于变量赋值
OBJS := foo.o bar.o
于是我们结合这两个内置函数,就可以将Makefile脚本改成下面的样子:
OUT := main
SRCS := $(wildcard *.c) #将当前目录下的所有.c文件的文件名以空格分割,然后赋值给SRCS变量
OBJS := $(patsubst %.c,%.o,$(SRCS)) #获取当前目录下所有.c文件对应的.o文件,以空格分割
COM_OP := -Wall -g
CC := gcc #修改CC的默认值
$(OUT):$(OBJS)
$(CC) $^ -o $@
%.o : %.c compute.h
$(CC) -c $< -o $@ $(COM_OP)
.PHONY: clean rebuild
clean:
$(RM) $(OUT) $(OBJS)
rebuild: clean $(OUT)
以上,我们就的得到了一个极具通用性的Makefile脚本。
更多推荐


所有评论(0)