首页 > 其他分享 >彻底掌握Makefile(一)

彻底掌握Makefile(一)

时间:2022-09-22 11:13:07浏览次数:80  
标签:gcc makefile 彻底 掌握 myprint demo make Makefile 编译

彻底掌握Makefile(一)

介绍

makefile就是一个可以被make命令解析的文件,他定义了一系列编译的规则,帮助我们更加方便、简洁的去完成编译的过程。在一个大工程当中我们会有各种各样的文件,我们可能会分模块去存放各种文件,可能有些文件还依赖其他的文件,因此我们在编译的时候需要先将被依赖的文件先编译,其他文件后编译,而我们使用makefile就可以更好的去完成这件事儿。

Makefile基础

在很多情况下我们在C/C++的项目当中使用makefile,其实在其他语言的项目也可以使用make和makefile,它不局限于语言的,可以是C也可以是Java。现在写一个基本的例子:

demo: demo.c
	gcc demo.c -o demo

clean:
	rm demo

上面是一个makefile的例子,我们简要说明一下makefile的书写规则:

编译目标:依赖文件
	编译命令

然后我们使用make命令去解释执行makefile,我们可以使用make 编译目标去执行特定的语句,比如在上面的例子当中我们执行make demo的话就会执行gcc demo.c -o demo命令。

其实上面我们也可以直接使用make命令,不需要指定编译目标,因为make会自己寻找第一个目标作为被执行的目标。

在上面的代码当中我们当我们执行make的时候寻找到makefile文件的第一个目标demo,但是因为我没有改动demo.c这个文件,而且这个文件已经编译过了,因此我们没有必要再去编译这个文件,这也是make给我们提供的一个非常好的特性,我们不需要重新编译已经编译好的文件,这在一个大型项目当中是非常有用的,当我们的项目当中有成千上万的文件的时候,如果我们重新编译每一个文件的话,那么编译的时间消耗是非常大的。因此我们在执行make执行执行clean编译目标先删除demo这个编译结果,然后在执行make这次它再找到demo目标,而此时demo已经被删除了,因此会重新编译。

Make命令的工作流程

当我们在命令行当中输入make的时候他的执行流程如下:

  • make命令首先会在当前目录下面寻找makefile或者Makefile文件。
  • 寻找到makefile文件之后,他会在文件当中寻找到一个编译目标,比如在上面的makefile文件当中他会找到demo这个编译目标,而不是clean这个目标,因为clean是第二个编译目标。
  • 然后make会解析编译目标的依赖,如果这个依赖是其他的编译目标A的话,那么make会先完成它依赖的编译目标A的命令,如果它依赖的编译目标A也存在依赖B的话,make就会去执行依赖的B的编译命令,如此的递归下去,知道有所得依赖目标都存在了,才会完成第一个编译目标的编译,这个也很好理解,只有依赖文件都存在了我们才能够完成正确的编译过程。

make编译的过程为寻找编译目标,依赖关系,如果依赖的文件还存在依赖那么make会一层一层的寻找下去,只要所有的依赖都被成功解析了,才会最终执行第一个编译目标的编译命令。但是在makefile当中不被第一个编译目标的目标的编译命令是不会被执行的,比如上面我们执行make的时候会执行demo编译目标,但是不会执行clean编译目标。

下面我们写一个例子了解make解析依赖的过程:

main: demo.o myprint.o
	gcc demo.o myprint.o -o out
	echo make 解析编译完成

demo.o: demo.c 
	gcc -c demo.c -o demo.o

myprint.o: myprint.c 
	gcc -c myprint.c -o myprint.o

clean:
	rm myprint.o demo.o out

执行make之后的结果如下图所示:

因为我们没有指定编译的目标,因此make会寻找一个编译目标,也就是main,但是在main当中依赖两个文件demo.omyprint.o,因此make会再去执行demo.omyprint.o两个编译目标,当这两个编译目标被完成之后才能执行main的编译,根据make的输出结果我们可以得到这一个结果的印证。

Makefilex小技巧

Makefile当中的变量

在makefile当中我们可以定义一些我们的变量这样就可以避免反复输入了。比如我们经常会变编译文件的时候增加一些编译选项,比如像下面这样:

cflags=-c
main: demo.o myprint.o
	gcc demo.o myprint.o -o out

demo.o: demo.c 
	gcc $(cflags) demo.c -o demo.o

myprint.o: myprint.c 
	gcc $(cflags) myprint.c -o myprint.o

clean:
	rm myprint.o demo.o out

在上面的makefile当中我们定义了一个变量cflags并且在编译命令当中使用,我们定义变量的方法其实和shell差不多,我们直接使用=可以定义变量,然后使用$(变量名)可以使用变量,因为上面的例子当中cflag=-c比较短,比较简单,但是如果当我们的编译参数很多很长的时候使用变量就非常有效了,而且如果在一个项目当中如果有成千上万个文件我们像统一改变编译时候的参数的话,我们一个一个改是很麻烦的,但是如果我们使用变量就可以做到一改全改。

Makefile当中的include命令

在makefile当中我们也可以使用include命令去包含其他的makefile文件,比如我们将上面的makefile文件分成两个部分makefilesubmakefile:

makefile:

include submakefile

demo.o: demo.c 
	gcc $(cflags) demo.c -o demo.o

myprint.o: myprint.c 
	gcc $(cflags) myprint.c -o myprint.o

clean:
	rm myprint.o demo.o out

submakefile:

cflags=-c
main: demo.o myprint.o
	gcc demo.o myprint.o -o out

然后在目录下执行make命令, 得到的效果和前文当中提到的makefile结果是一样的,这就相当于将submakefile的内容放到include语句的位置。

Makefile中的PHONY

在上面谈到的makefile当中有一个clean的编译目标用于清除我们编译的结果文件,现在我们在当前的目录下面增加一个文件clean在执行make命令,我们的makefile文件如下:

cflags=-c
main: demo.o myprint.o
	gcc demo.o myprint.o -o main

demo.o: demo.c 
	gcc $(cflags) demo.c -o demo.o

myprint.o: myprint.c 
	gcc $(cflags) myprint.c -o myprint.o

clean:
	rm myprint.o demo.o main

然后执行下面的命令(touch命令是新增一个文件,touch clean 就是往目录中增加一个名字为clean的文件):

我们可以看到当目录下面增加一个clean文件之后,我们使用make clean命令的时候,make给我们的提示信息为clean文件是最新的了。这是因为当执行make 编译目标,make首先会检查当前目录下面是否存在clean文件如果不存在则执行编译目标clean的命令。如果存在clean文件的话,make会检查编译目标clean的依赖文件是否发生更改,如果发生更改了那么就会执行clean对应的命令,但是在上面的makefile当中clean没有依赖文件,因此相当于他的依赖文件没有发生改变,make不会执行编译目标clean对应的命令了。

但是我们的需求是仍然希望执行clean之后的命令,这个时候我们就可以使用PHONY了,他可以保证即使存在clean文件的时候,make命令依然会执行编译目标clean对应的命令。

cflags=-c
main: demo.o myprint.o
	gcc demo.o myprint.o -o main

demo.o: demo.c 
	gcc $(cflags) demo.c -o demo.o

myprint.o: myprint.c 
	gcc $(cflags) myprint.c -o myprint.o

clean:
	rm myprint.o demo.o main
.PHONY: clean # 增加这一行

执行结果如下图所示:


我们现在来测试一下当我们命令生成的文件和编译目标不同名的时候,make是如何解释执行makefile的,makefile的内容如下:

cflags=-c
main: demo.o myprint.o
	gcc demo.o myprint.o -o out # 这里的输出文件是 out 而不是 main 输出文件名字和目标不同名

demo.o: demo.c 
	gcc $(cflags) demo.c -o demo.o

myprint.o: myprint.c  
	gcc $(cflags) myprint.c -o myprint.o

clean:
	rm myprint.o demo.o main
.PHONY: clean

执行结果:

从上面的结果我们可以发现,当我们编译的结果文件(out)和编译目标(main)不同名的时候make每次都回去执行这个目标,因为make在进行检测的时候没有发现main这个文件,因此每次执行make命令的时候都会去执行这个编译目标。

Makefile的通配符

我们现在修改前面的makefile,修改的结果如下(当前目录下面有两个文件demo.c和myprint.c):

cflags=-c

main: demo.o myprint.o
	gcc demo.o myprint.o -o main

%.o: %.c 
	gcc $(cflags) $<
clean:
	rm myprint.o demo.o main
.PHONY: clean

上面的makefile当中有一个通配符%,其中%.c表示当前目录下面的所有的以.c结尾的文件。上面的makefile与下面的makefile的效果是一样的:

cflags=-c

main: demo.o myprint.o
	gcc demo.o myprint.o -o main

demo.o:demo.c
	gcc $(cflags) demo.c 

myprint.o:myprint.c
	gcc $(cflags) myprint.c 

clean:
	rm myprint.o demo.o main
.PHONY: clean

在上面的makefile当中 $< 表示第一个依赖文件,上面的%就是一个通配符,%.c可以匹配任何以.c结尾的文件,你可能会有疑问%.c匹配不是所有的.c文件吗?那么等价的结果不应该是:

cflags=-c

main: demo.o myprint.o
	gcc demo.o myprint.o -o main

demo.o myprint.o:demo.c myprint.c
	gcc $(cflags) demo.c 

clean:
	rm myprint.o demo.o main
.PHONY: clean

事实上你可以认为make会将通配符匹配的文件一一展开,有几个文件就将产生对应数目的编译目标,而不是将他们都放在一起。

Makefile文件自动搜索

在一个工程项目当中我们可以会有许多的目录以及源文件,而make命令只会自动搜索当前目录下的文件,如果当前目录下没有那么make命令就会产生错误。因此make也给我们提供了一种文件搜索的功能。

在makefile当中,我们可以使用VAPTH指定make去搜索文件的路径。我们先来测试一下当我们没有使用VPATH的时候指定其他目录下的文件会出现什么情况,我们的文件目录结构如下图所示:

我们的makefile内容如下(先把VPATH的那一行注释掉):

cflags=-c

# VPATH=./files

main: demo.o myprint.o a.o b.o
	gcc demo.o myprint.o a.o b.o -o main

demo.o:demo.c
	gcc $(cflags) demo.c 

myprint.o:myprint.c
	gcc $(cflags) myprint.c 
a.o: a.c
	gcc $(cflags) a.c
b.o: b.c 
	gcc $(cflags) b.c

clean:
	rm myprint.o demo.o main
.PHONY: clean

执行结果如下图所示:

我们现在修改我们的makefile文件如下:

cflags=-c

VPATH=./files

main: demo.o myprint.o a.o b.o
	gcc demo.o myprint.o a.o b.o -o main

demo.o:demo.c
	gcc $(cflags) demo.c 

myprint.o:myprint.c
	gcc $(cflags) myprint.c 

a.o: a.c
	gcc $(cflags) $<
b.o: b.c 
	gcc $(cflags) $<

clean:
	rm myprint.o demo.o main
.PHONY: clean

再次执行make命令,执行结果如下图所示:

我们可以看到仍然出错了,这是因为虽然make可以找到a.c和b.c的位置,但是在执行编译命令的时候我们执行的依然是gcc -c a.c,这条命令是会在当前目录下去寻找a.c的,而不会在其他路径下去寻找a.c,在这里我们可以使用$<去解决这个问题,$<表示第一个依赖的文件。

修改之后的makefile文件如下所示:

cflags=-c

VPATH=./files

main: demo.o myprint.o a.o b.o
	gcc demo.o myprint.o a.o b.o -o main

demo.o:demo.c
	gcc $(cflags) demo.c 

myprint.o:myprint.c
	gcc $(cflags) myprint.c 

a.o: a.c
	gcc $(cflags) $<
b.o: b.c 
	gcc $(cflags) $<

clean:
	rm myprint.o demo.o main a.o b.o
.PHONY: clean

执行结果如下图所示:

如果你想设置多条目录的话,设置的规则为VPATH=dir1:dir2:dir3:dir4

总结

在本篇文章当中主要介绍了一些makefile的基础用法,和一些常用的小技巧,虽然不是很难,但是掌握了可以很大的跳高我们的工作效率,也可以方便我们阅读别人写的makefile文件。


以上就是本篇文章的所有内容了,我是LeHung,我们下期再见!!!更多精彩内容合集可访问项目:https://github.com/Chang-LeHung/CSCore

关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。

标签:gcc,makefile,彻底,掌握,myprint,demo,make,Makefile,编译
From: https://www.cnblogs.com/Chang-LeHung/p/16718483.html

相关文章

  • Linux之sed练习掌握
    1。操作文本的内容。catsedtest.txt[root@ecs-76840553sed]#catsedtest.txtThisistheheaderline.Thisisthefirstdataline.Thisistheseconddatal......
  • 花一个小时快速掌握Jest的所有知识点~
    本文为稀土金块技术社区的第一篇署名文章。14日内禁止转载,14日后禁止擅自转载。侵权必究!大家好,我是小嘟嘟,我们知道常用的测试类型有:功能测试,单元测试,集成测试,......
  • 知识怎么样才算掌握
    1、最基本的就是考试的手段,通过考试,无论题目怎么变化都可以考高分,证明知识掌握了;2、能够熟练的运用,不仅仅限于书本,比如可以通过书本的只是来解决现实生活和工作中的问题就......
  • 正则表达式的掌握
    正则表达式入门指南,看这篇就够了!点击关注......
  • 阿里云EMAS移动测试,帮您快速掌握移动端兼容性测试技巧
    简介: 兼容性测试用于验证应用在不同设备上进行安装/启动/登录/不同版本覆盖安装/卸载等操作时,是否存在兼容性问题;如界面适配问题、应用性能等,现阿里云EMAS套餐免费试用,帮......
  • 带你掌握如何使用CANN 算子ST测试工具msopst
    摘要:本期带您了解如何使用msopst工具。本文分享自华为云社区《【CANN文档速递13期】算子ST测试工具【msopst】》,作者:昇腾CANN。如何获取msopst工具msopst工具存储在As......
  • 彻底搞懂this指向
    this是JavaScript中的一个关键字,但是又一个相对比较特别的关键字,不像function、var、for、if这些关键字一样,可以很清楚的搞清楚它到底是如何使用的。this会在执行上下文......
  • Java 并发编程解析 | 如何正确理解Java领域中的并发锁,我们应该具体掌握到什么程度?
    苍穹之边,浩瀚之挚,眰恦之美;悟心悟性,善始善终,惟善惟道!——朝槿《朝槿兮年说》写在开头对于Java领域中的锁,其实从接触Java至今,我相信每一位JavaDeveloper都会有这样......
  • 一篇文章彻底搞懂snowflake算法及百度美团的最佳实践
    写在前面的话一提到分布式ID自动生成方案,大家肯定都非常熟悉,并且立即能说出自家拿手的几种方案,确实,ID作为系统数据的重要标识,重要性不言而喻,而各种方案也是历经多代优化......
  • TransUNet——彻底改变传统的图像分割
    TransUNet——彻底改变传统的图像分割通过结合CNN和Transformer对U-Net进行改造,以在图像分割任务上实现SOTA结果。目录·直觉·TransUNet∘下采样(编码......