目录
一、背景
二、makefile和make的讲解
2.1 使用方法
2.2 伪目标文件
2.3 文件的属性以及属性的更新
2.4 makefile的自动推导
一、背景
这里会提及为什么要使用makefile和make,以及他们是什么和作用。
- 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
- make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
简单来说:
makefile 是一个在当前目录下存在的一个具有特定格式的文本文件
make是一条指令
二、makefile和make的讲解
2.1 使用方法
既然makefile和make是这么好的一个工具,那我们该如何去使用他呢。莫急,且听我与你细细道来。首先在上文我们就说了,makefile是一个文件,那我们就得创建一个文件,且其命名必须为makefile(首字母可以大写)。例如我编写的这个:
文件创建好了,那么其内容我们该怎么去编写呢,先看例子:
这里先简单的说明一下这段代码是什么意思,随着我之后的讲解你们会逐渐地理解。
首先第一行代码,表明mybin和code.c具有依赖关系呢,这个大家慢慢就可以理解,其中 :左边的文件主要依赖于右边的文件,:右边可以有多个文件(先简单这样理解)。
第二行代码,表面第一行依赖关系的依赖方法,通过这行代码我们也可以看出通过gcc指令将code.c编译生成mybin文件。
第三行代码,表示clean是一个伪目标,这个下文我们会具体的说明。
第四行代码,表示clean没有具体的依赖文件。
第五行代码,同样表示其依赖方法,这里主要是删除mybin这个可执行文件。
接下来,让我们看看效果:
可以发现,我们仅仅通过make指令就形成了我们的可执行文件,同样我们还通过make clean指令删除了我们的mybin文件。
我相信细心的同学肯定会发现,为什么我们生成mybin文件的时候不需要写成make mybin呢?
这是因为当我们输入make指令时,其会自动在Makefile文件中从上自下的去检索第一个有效指令。如果我们把clean和mybin换一下位置,结果可就不一样了哦,大家可以自行尝试一下。
我们其实还可以用其他方式来编写我们的makefile,这里我直接给大家上代码,再根据代码进行介绍。
在编写makefile时,我们可以像前三行一样,去给我们的文件重新起一个名字,使用格式为
$(+命名名字)。
在看第一个关联关系,出现一个 $^ 和一个 $@ ,那这两个符号是什么意思呢。
$^ :表示关联关系的右边 即 :的右边
$@:表示关联关系的左边 即 :的左边
2.2 伪目标文件
在刚刚的代码中我们看到了这样一段代码:
首先将结论:
.PHONY 将clean修饰成了一个伪目标
那么这个伪目标有什么作用呢
作用:当被修饰成伪目标后,该指令可以总是被执行(依赖方法总是被执行的,不会被任何方式所拦截)
为什么要说可以总是被执行呢,难道系统还会拦截我们的指令不成,事实上确实时会的。例如:
可以发现我们的make mybin在执行一次之后就不在让我们去执行了,而make clean 却可以反复的去执行。那这是为什么呢,系统是如何去拦截我们的命令的呢?,请看下文。
2.3 文件的属性以及属性的更新
首先我们来介绍一个指令:
stat + 文件名 ------------------可以查看一个文件的属性
例如我们现在就来看看刚刚通过make指令创建的code.c文件:
我们可以看到在文件中有很多属性,这里我们具体来说是后面三个。
Access : 访问时间。
Modify:最近一次文件内容被修改的时间
Change:最近一次文件属性被修改的时间
注意:文件 = 内容 + 属性
回归正题,为什么make/makefile总是不让我们重新编译代码?
其实这是为了去提高我们的编译效率。如果我们的源代码什么都没改,那么我们一直的反复编译时没有任何意义的,所以其总是不让我们重新编译代码。
那么其是怎么做到这一点的呢?
主要通过一个文件的修改时间,光一个时间有什么用呢。所以时间并不是本质,通过时间对比出文件的新旧才是本质(更新后的文件才有重新编译的价值)。那么其和谁去对比呢?不要忘了,我们生成的可执行程序也是一个文件,要知道重新编译的本质不就是重新写入一个二进制可执行文件吗,那么其的修改时间就一定会更改。
这里还有一个逻辑,第一次编译的时候,一定是先有的源文件,才有的bin文件,那么
源文件的修改时间一定会小于bin文件的修改时间。那么在之后,我们对源文件做修改的时候一定有,源文件的修改时间大于bin文件的修改时间(此时还没进行编译)。这样不就可以对比出文件的新旧,进而去达到防止我们对一个文件反复编译的效果了吗。
这里重新介绍一下touch,其还可以更新一个文件的时间。可以用man指令进行查看。(我这只截取了一部分)
可以看到 -a是修改访问时间,-m是修改mtime。
我们在这里可以通过这个指令来印证上面我们所说的。
这个时候是不让make的:
其时间属性如下:
接着我们touch一下,然后再次观察code.c的时间属性:
可以很明显的发现其发生了变化,我们在试一试可不可以make:
实验成功,刚好印证了我们上面所说。
补充:
我们所看到的文件,一般在哪里存放着呢?一般是放在磁盘,更改文件时间的本质其实就是访问磁盘。其实系统不是很愿意去访问外设的,因为很慢,会导致效率的降低。但是一般而言,一个文件被查看的频率是非常高的(不是用户访问),如果每次都要去更改Access的话,会导致Linux系统充满大量的访问磁盘的IO操作,变相的减慢系统效率。为了减少这样的访问,系统设置了一个每经过一定次数的查看,再进行一次修改Access的操作。
2.4 makefile的自动推导
这里我们同样先用代码举例:
这段代码很有意思,当我们要创建mybin时,目录中却没有code.o,怎么办,那么他就会在makefile中从上至下的去找,看有没有关于code.o的依赖关系,很明显根据这段代码他一直会往下找,直到遇到code.c(该文件在目录中存在),这往下找的过程其实可以看做时一个入栈的过程,而当找到code.c,那么接下来就会相当于一个出栈的过程一直生成所要的文件。
这部分过程可以看做makefile/make的推导能力(当然这只是很小的一部分)。