Makefile轻松入门

一、Makefile简介

  在软件开发中,make通常被视为一种软件构建工具。该工具主要经由读取一种名为“makefile”或者“Makefile”的文件来实现软件的自动化构建。它会通过一种被称之为“target”概念来检查相关文件之间的依赖关系,这种依赖关系的检查系统非常简单,主要通过对比文件的修改时间来实现。在大多数情况下,我们主要用它来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或库文件。

二、Makefile中的规则

  makefile由三个部分组成,分别是目标、依赖和命令。格式如下:

目标:依赖
	命令

注意:命令前面有tab缩进!
  makefile就是由一条或者多条这样的规则组成。

三、makefile的编写

  假如有一份主程序代码(main.c),里面调用了三个函数,分别定义在add.c、sub.c、mul.c文件中,并且它们都声明在头文件(head.h)中。通常情况下,我们需要这样编译它:

gcc main.c add.c sub.c mul.c -o app

  如果没有makefile文件,在开发+调试程序的过程中,我们就需要不断地重复输入上面这条编译命令,要不就是通过终端的历史功能不停地按上下键来寻找最近执行过的命令。这样做有两个缺陷:
1、一旦终端历史记录被丢失,我们就不得不从头开始;
2、任何时候只要我们修改了其中一个文件,上述编译命令就会重新编译所有的文件,当文件足够多,就会非常耗时。
  这个时候,makefile就派上用场了,先来看看最简单的makefile文件。

第一版本:

app:main.c add.c sub.c mul.c
	gcc main.c add.c sub.c mul.c -o app

  这就是最简单的makefile文件,app是目标,add.c、sub.c、mul.c是其依赖,gcc main.c add.c sub.c mul.c -o app就是命令。保存该makefile文件,然后在当前目录下,输入make命令,就会看到它按照我们的设定去编译程序了。
在这里插入图片描述  这就避免了缺陷1,但是这种版本的makefile文件效率低,如果修改一个文件,所有的文件会被全部重新编译。假如文件很多,整个编译过程就会非常耗时。下面就来看看第二版本的makefile文件。

第二版本:

app:main.o add.o sub.o mul.o
	gcc main.o add.o sub.o mul.o -o app

main.o:main.c
	gcc main.c -c

add.o:add.c
	gcc add.c -c

sub.o:sub.c
	gcc sub.c -c

mul.o:mul.c
	gcc mul.c -c

  这个版本的makefile比第一版本的就稍微复杂一点,但是也没有复杂很多,我们来对比一下,第一版本的makefile只有一条规则,而第二版本的总共有5条规则,每条规则里面都一样,包含目标,依赖,命令这三个部分。
  需要知道的是,不管makefile里面有几条规则,最前面的那条规则是整个makefile最主要的规则,而这条规则中的目标是这个makefile要生成的终极目标。
  工作原理:在执行第一条规则中的命令之前,检测该目标的依赖是否存在,如果依赖不存在,就向下搜索规则,如果有规则是用来生成该目标的依赖,就执行规则中的命令。比如:gcc main.c -c、gcc add.c -c
、gcc sub.c -c、gcc mul.c -c这些就是用来生成终极目标app的依赖。它们也是由其对应的依赖,根据命令编译生成出来的。如果依赖已经存在,就判断是否需要更新,(判断的原则:目标时间 > 依赖时间,就不更新;反之,就更新目标)。

如果app的全部依赖都不存在,执行make命令之后,如下图所示:
在这里插入图片描述
如果app的全部依赖都存在,但是后来又修改了add.c这个文件,执行make之后,如下图所示:
在这里插入图片描述
  你看,现在这个版本就不存在文件多,编译耗时这个问题了。因为如果文件有修改,就会判断是否需要更新。当然,也还是不够完美,那就是冗余,看到了嘛?下面的四条规则中,除了目标和依赖不一样之外,其他都一样,那么,我们有没有办法让它们合并在一起呢?下面就来看看第三版本的makefile。

第三版本:

  在写第三个版本之前,先介绍一下,makefile是有变量的,但是因为makefile是脚本语言,变量是没有数据类型的,不像C、C++这些语言。

obj=main.o add.o sub.o mul.o    #自定义变量
aa=$(obj)                       #变量的取值

  makefile有自带的变量,都是大写的,比如:CPPFLAGS、CC…当然,你也可以对其修改。
  makefile中还提供一种自动变量:
● $@: 规则中的目标;
● $<: 规则中的第一个依赖;
● $^: 规则中所有的依赖。
注意:这些自动变量,只能在规则的命令中使用。
  现在我们来看一下第三版本的makefile。

obj=main.o add.o sub.o mul.o
target=app
$(target):$(obj)
	gcc $(obj) - o $(target)

%.o:%.c
	gcc -c $< -o $@

  规则还是原来的规则,只是使用了变量来替换。需要注意的是,自定义变量的时候,不需要指定数据类型,取变量值的时候要加上$和()。
  这里需要重点介绍的是,%.o:%.c,这是模式匹配,意思是,如果app的依赖项main.o不存在的话,就匹配生成。

main.o:main.c
	gcc -c main.c -o main.o

app的其他依赖项如果不存在的话,也是进行同样的匹配,然后执行相应的命令。执行make命令之后,结果如下:
在这里插入图片描述
  可以对比第二版本,执行结果都一样,但是第三版本使用了变量和模式匹配,所以不存在冗余。但是这个版本还是不够完美,那就是可移植性差。接下来看看第四版本。

第四版本:

  在编写第四版本的makefile之前,先介绍两个函数(注:makefile所有的函数都有返回值):
第一个是wildcard,查找指定目录下指定类型的文件,src = $(wildcard ./src/*.c)等效于src = ./src/a.c ./src/b.c ./src/c.c。
第二个函数是patsubst,匹配替换,obj = $(patsubst %.c, %.o, $(src))等效于obj = ./src/a.o ./src/b.o ./src/c.o。

src=$(willcard ./*.c)
obj=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(obj)
	gcc $^ -o $@

%.o:%.c
	gcc -c $< -o $@

执行结果依旧和第二版本的一样,如下图:
在这里插入图片描述
  现在这个版本的makefile,放在同样目录结构的另外一个项目里面,也同样可以使用,这就实现了可移植性。看似很完美了,但其实还是有缺陷,如果我们要清理项目,就需要每次都在终端上敲rm *.o app。那能不能让makefile来帮我们做这个事情呢?来看看第五版本的makefile。

第五版本:
  在编写第五版本的makefile之前,我们需要知道,如果要让make生成不是终极目标的目标,需要在终端输入“make 目标名”。现在我们就可以在第四版本的最后面,编写一个清理项目的规则:

.PHONY:clean
clean:
	-rm $(target) $(obj) -f

执行make clean命令之后,结果如下图所示:
在这里插入图片描述
现在这就是一个比较完善的makefile了。

四、总结

  当然,这只是一个十分简单的例子的makefile文件完善过程,通过这个过程的学习,可以自己写一个简单项目的makefile文件,后期通过进阶学习,再不断的完善,也可以大概看懂别人的一些简单项目的makefile。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值