Makefile学习笔记

目录

 一、前言

二、什么是Makefile?

三、Makefile文件命名和规则。 

3.1 文件命名

3.2 Makefile规则

四、工作原理

4.1 检查依赖

4.2 检查更新

五、变量

5.1 自定义变量

5.2 预定义变量

六、模式匹配

七、函数

7.1 wildcard函数

7.2 patsubst函数


 一、前言

        linux环境下,当一个项目中源文件过多,手动逐个编译明显不切实际,使用Makefile脚本文件可以帮助我们模块化编译文件,本篇文章由浅入深,层层递进,可帮助读者逐步掌握Makefile的编写。

二、什么是Makefile?

       众所周知,一个大型工程中的源文件成百上千,其中包含各种.c和.h文件。Makefile文件就定义了一系列的规则指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译。Makefile带来的好处就是“自动化编译”,一旦写好,只需要执行一个make指令,整个工程就能完成自动编译,不再需要手动逐个编译。

        如下图所示,在一个src目录中,包含很多源代码文件,我们手动逐个编译非常不便,其次我们还要考虑源代码文件的编译顺序。

但如果我们编写一个如下图所示的Makefile文件,则编译过程会简单很多,每一次编译只需要输入make指令则可以根据Makefile中的命令自动化地去编译,最终得到想要地可执行程序。

三、Makefile文件命名和规则。 

3.1 文件命名

        文件名必须为makefile或Makefile

3.2 Makefile规则

        目标 ... :  依赖 ...

                命令1

                命令2

                ...

目标:最终要生成的文件

依赖:生成目标所需要的文件或目标

命令:通过命令对依赖操作生成目标(命令前必须Tab缩进)

写法1:

/*
现有源文件 main.c sub.c add.c mult.c div.c head.h, 需要生成可执行程序 app
*/

/* 例1:若有一个目标,多个依赖,一条命令,Makefile如下所示。 */
app : sub.c add.c mult.c div.c main.c
    gcc sub.c add.c mult.c div.c main.c -o app

/* 例2:若有多个目标,多个依赖,多条命令,Makefile如下所示。 */
app1 app2 : sub.c add.c mult.c div.c main.c
    gcc sub.c add.c -o app1
    gcc mult.c div.c main.c -o app2

四、工作原理

4.1 检查依赖

        命令在执行之前, 先检查规则中的依赖是否存在。

        (1) 若存在,则执行命令;

        (2) 若不存在,则向下检查其他的规则,检查有没有一个规则用来生成这个依赖,若有则执行该规则中的命令。

 写法2: 

/*第一条规则*/
app : sub.o add.o mult.o div.o main.o
    gcc sub.o add.o mult.o div.o main.o -o app

/*为第一条规则服务*/
sub.o : sub.c
    gcc -c sub.c -o sub.o
add.o : add.o 
    gcc -c add.c -o add.o
mult.o : mult.c
    gcc -c mult.c -o mult.o
div.o : div.c
    gcc -c div.c -o div.o
main.o : main.c
    gcc -c main.c -o main.o

        相比于写法1,写法2虽然看起来更“麻烦”了,但写法1每一次执行make都会把所有文件重新编译,而写法2利用了Makefile的检查更新功能,会自动检测目标和依赖的时间戳,这样就可以每次只编译修改的源文件,大大提升了效率。

4.2 检查更新

        在执行规则中的命令时,比较目标和依赖文件的时间。

        (1) 若依赖的时间比目标的时间晚, 需要重新生成目标;

        (2) 若依赖的时间比目标时间早,目标不需要更新, 对应规则中的命令也不需要被执行;

五、变量

5.1 自定义变量

/*变量定义方法:变量名 = 变量值*/
var = abc; /* 定义一个变量,变量名为var,变量值为abc */
$(var);  /* 利用$获取变量var的值 */

5.2 预定义变量

AR : 归档维护程序的名称,默认值为ar
CC : C Complier, 默认值为cc
CXX : C++ Complier, 默认值为g++
$@ : 目标的完整名称
$< : 第一个依赖文件的名称
$^ : 所有的依赖文件
/* 【注】下面三个为自动变量,只能在规则的命令中使用 */

 写法3

/* 利用变量编写Makefile */

src = sub.o add.o mult.o div.o main.o
target = app
$(target) : $(src)
    $(CC) $(src) -o $(target)

sub.o : sub.c
    gcc -c sub.c -o sub.o
add.o : add.c
    gcc -c add.c -o add.o
mult.o : mult.c
    gcc -c mult.c -o mult.o
div.o : div.c
    gcc -c div.c -o div.o
main.o : main.c
    gcc -c main.c -o main.o

        相比于写法2,写法3利用变量对第一条规则做出了适当的简化,但其他规则仍然需要优化(比如一百个规则,我们就要手动写一百次,该部分可利用模式匹配进行优化)。

六、模式匹配

% : 表示通配符, 匹配一个字符串,一个语句中两个'%'表示同一个字符串。

 写法4:

/* 定义变量 */
src = sub.o add.o mult.o div.o main.o
target = app

/* 第一条规则 */
$(target) : $(src)
    $(CC) $(src) -o $(target)

/* 其他规则 */
%.o : %.c
    $(CC) -c $< $@

        相比于写法3,写法4对规则都进了很大程度的优化,但在定义变量时仍然存在不足(需要手动去把src逐个列出来)。

七、函数

7.1 wildcard函数

$(wildcard PATTERN...)
/*
功能:获取指定目录下指定类型的文件列表;
参数:PATTERN指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔;
返回值:若干个文件的文件列表,文件名之间用空格间隔;
*/

示例:
$(wildcard *.c ./sub/*.c) // 获取当前目录下的.c文件以及当前目录中sub目录下所有.c文件

7.2 patsubst函数

$(patsubst <pattern>, <replacement>, <text>)
/*
功能:查找<text>中的单词是否符合<pattern>,若匹配,则以<replacement>替换;
返回值:被替换后的字符串
*/

示例:
$(patsubst %.c, %.o, x.c bar.c)  // 讲x.c和bar.c替换为x.o和bar.o

 写法5:

/* 获取所有.c文件 */
src = $(wildcard ./*.c)
/* 将所有的.c替换成.o文件 */
objs = $(patsubst %.c, %.o, $(src))
target = app

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

        相比于写法4,写法5对变量定义部分做了优化,但上述写法仍然存在不足,会产生大量的.o文件,可能会造成文件冗余,如下图所示。

 写法6:

src = $(wildcard ./*.c)
objs = $(patsubst %.c, %.o, $(src))
target = app
$(target) : $(objs)
    $(CC) $(objs) -o $(target)

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

clean:
    rm $(objs) -f

        写法6中增加的clean相关语句可以自动地帮我们删除多余地.o文件,但这个写法仍存在问题,如果项目目录中添加一个叫做 clean 的文件(与规则中的目标名称相同),那么就会造成这个规则无法正常使用,如下图所示:

        这里出现的问题对应前面第四大点的检查更新机制,由于规则中clean的依赖为空,这会导致目标的时间戳总是大于依赖的时间戳,规则中的命令就不再执行。解决这个问题的方法就是在 Makefile 文件中利用.PHONY 关键字声明 clean 是一个伪目标,让 make 不再对它的时间戳检测,如写法7所示。

写法7:

src = $(wildcard ./*.c)
objs = $(patsubst %.c, %.o, $(src))
target = app
$(target) : $(objs)
    $(CC) $(objs) -o $(target)

%.o : %.c
    $(CC) -c $< -o $@
  
/* 申明方式 PHONY:伪文件名称 */
.PHONY: clean
clean:
    rm $(objs) -f
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值