彻底掌握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.o和myprint.o,因此make会再去执行demo.o和myprint.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文件分成两个部分makefile和submakefile:
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、计算机系统基础、算法与数据结构)知识。
彻底掌握Makefile(一)的更多相关文章
- 说说Makefile那些事儿
说说Makefile那些事儿 |扬说|透过现象看本质 工作至今,一直对Makefile半知半解.突然某天幡然醒悟,觉得此举极为不妥,只得洗心革面从头学来,以前许多不明觉厉之处顿时茅塞顿开,想想好记性不 ...
- 编写一个通用的Makefile文件
1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...
- 编写简单的Makefile文件
makefile中的编写内容如下: www:hello.c x.h gcc hello.c -o hello clean: rm hello www:hello.c x.h 表示生成www这个文件需 ...
- 简单编写Makefile
相信很多朋友都有过这样的经历,看着开源项目中好几页的makefile文件,不知所云.在日常学习和工作中,也有意无意的去回避makefile,能改就不写,能用ide就用ide.其实makefile并没有 ...
- [转]Linux中configure/makefile
本文教你如何使用autoconf.automake等来制作一个以源代码形式(.tar.gz)发布的软件.并可在执行configure时使用自定义参数. 一.概述和基础知识 在Linux下得到一个以源代 ...
- Linux内核配置、编译及Makefile简述
Hi,大家好!我是CrazyCatJack.最近在学习Linux内核的配置.编译及Makefile文件.今天总结一下学习成果,分享给大家^_^ 1.解压缩打补丁 首先是解压缩你获取到的Linux内核. ...
- make 查找的文件名顺序为:“GNUmakefile”、“makefile”、“Makefile”
默认的情况下,make会在工作目录(执行make的目录)下按照文件名顺序寻找makefile文件读取并执行,查找的文件名顺序为:“GNUmakefile”.“makefile”.“Makefile”. ...
- 实例:对2个Makefile的备注
实例1:Makefile编译链接简单.c函数 example.c Makefile exe: example.c gcc example.c -o exe clean: rm exe 执行效果: 实例 ...
- Linux中C程序调试、makefile
gcc基本语法格式:gcc [-选项] 源文件 [-选项] 目标文件,GCC编译C程序的过程: 预处理:gcc -E hello.c hello.i.-E指定执行到预处理结束,下面类似. 编译:gcc ...
- Linux工具入门:make工具与Makefile文件
1. make工具 利用make工具可以自动完成编译工作,这些工作包括: 如果修改了某几个源文件,则只重新编译这几个源文件 如果某个头文件被修改了,则重新编译所有包含该头文件的源文件 利用这种自动编译 ...
随机推荐
- 如果一个promise永不resolve,会内存泄漏吗
答:跟内存泄漏没有直接关系gc的策略不会改变,如果该promise没有被人引用,就会被gc掉.如果仍被引用,就不会被gc掉.即使一个promise,resolve或者reject了,但是它还被人引用, ...
- MyBatis 映射文件
Mybatis映射文件简介 1) MyBatis 的真正强大在于它的映射语句.由于它的异常强大,映射器的 XML 文件就显得相对简单.如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉 ...
- loguru备忘
loguru是个非常好用的三方日志管理包,简单易用,奈何老是记不住,在这记录一下吧 #coding:utf-8 ''' @version: python3.8 @author: 'eric' @lic ...
- ModuleNotFoundError: No module named 'distutils.spawn'
解决办法: 安装python3-distutils sudo apt-get install python3-distutils
- 使用 Azure 静态 Web 应用服务免费部署 Hexo 博客
一.前言 最近在折腾 Hexo 博客,试了一下 Azure 的静态 Web 应用服务,发现特别适合静态文档类型的网站,而且具有免费额度,支持绑定域名.本文只是以 Hexo 作为示例,其他类型的框架也是 ...
- 并查集和kruskal最小生成树算法
并查集 先定义 int f[10100];//定义祖先 之后初始化 for(int i=1;i<=n;++i) f[i]=i; //初始化 下面为并查集操作 int find(int x)//i ...
- zabbix监控添加学习笔记
在实际生产环境中,除了CPU.内存等一些系统信息可以挂载zabbix的自带模板Template OS Linux:但是一些公司开发的定制服务需要自己写模板或者监控项去监控: 一.监控公司的java服务 ...
- docker容器技术基础入门
目录 docker容器技术基础入门 容器(Container) 传统虚拟化与容器的区别 Linux容器技术 Linux Namespaces CGroups LXC docker基本概念 docker ...
- HDFS基础入门
HDFS简介 HDFS(全称:Hadoop Distribute File System)分布式文件系统,是Hadoop核心组成. HDFS中的重要概念 分块存储 HDFS中的文件在物理上是分块存储的 ...
- HDU3085 Nightmare Ⅱ (双向BFS)
联赛前该练什么?DP,树型,状压当然是爆搜啦 双向BFS就是两个普通BFS通过一拼接函数联系,多多判断啦 #include <iostream> #include <cstdio&g ...