一、背景知识
会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中。makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
make是一个命令工具,是一个解释makefile中指令的命令工具;
make
是一条命令,makefile
是一个文件,两个搭配使用,完成项目自动化构建。
二、使用方法
1、基本使用流程
(1)先在当前目录利用touch
指令建立一个名为makefile或Makefile的文件
(2)提前用vim编辑器在同目录下建立好一个.c源文件用于测试
(3)接着再通过vim编辑器,在Makefile文件中写上相应的依赖关系和依赖方法(后文会解释这两个名词)
(4)最后执行make命令后,会执行makefile文件中对应的依赖方法,生成了一个可执行文件,能执行并正确输出
(5)也可执行make clean
的命令用于对可执行文件的清理,因为更改代码后,可执行文件需要重新生成
由上可得,Makefile文件中的内容可抽象为:
依赖关系名: 依赖对象名
依赖方法
补充使用技巧:
可用特殊符号$@
和$^
分别表示冒号左边和右边内容,如:
testcode: testcode.c
gcc $^ -o $@
就可写为:
testcode: testcode.c
gcc testcode.c -o testcode
在依赖方法前加上@
后,make依赖关系后,对应的依赖方法不会回显。
(PS:make指令使用的完整格式应为make+Makefile中写的依赖关系名;省略时默认执行第一个依赖关系)
2、相关名词
下面对基本使用流程中出现的名词做一下说明:
(1)依赖关系
如上流程(3)中标记所示,其中的testcode
和clean
都称作依赖关系;不同的是testcode
有具体的依赖对象(一般都是文件,故也可称为依赖文件),而clean没有。
testcode: testcode.c
如上就表示testcode
依赖于testcode.c
,
(2)依赖方法
如上流程(3)中标记所示,依赖关系下的一条指令就是该依赖关系所对应的依赖方法。make+依赖关系时就会执行依赖方法部分的指令内容;故语句的执行
testcode: testcode.c
gcc testcode.c -o testcode
就可理解为:testcode
依赖于testcode.c
,当依赖关系testcode被make时,make会在与Makefile同目录下去找相应的依赖对象,也就是testcode.c
;若找到,则执行相应的依赖方法;否则直接报错
三、相关原理
1、执行原理
若有如下这么几组依赖关系:
(PS:这里根据编译链接过程特意写复杂进行验证,实际直接gcc生成可执行文件即可)
1 testcode : testcode.o
2 gcc testcode.o -o testcode
3 testcode.o : testcode.s
4 gcc -c testcode.s -o testcode.o
5 testcode.s : testcode.i
6 gcc -S testcode.i -o testcode.s
7 testcode.i : testcode.c
8 gcc -E testcode.c -o testcode.i
其中的依赖关系是:
testcode
依赖于testcode.o
;testcode.o
依赖于testcode.s
;
testcode.s
依赖于testcode.i
;testcode.i
依赖于testcode.c
执行make之后:
可以看到,系统会先找到文件Makefile,再从文件中搜索依赖关系,按各组依赖关系间的彼此依赖顺序执行依赖方法。如上就会先执行gcc -E testcode.c -o testcode.i
生成test.i
,然后根据依赖关系逐步执行其他相关依赖方法,直至生成最终目标testcode
(PS:这四组依赖关系的顺序可以打乱,打乱之后如果想生成testcode
的可执行文件而该组依赖关系又不在Makefile文件的最开始时,就需把make命令写成:make testcode
。因为前面说过啦,make后不加依赖关系默认执行的是第一个依赖关系)
下面还有一个问题:
为什么make一次就不能make了?
解答:我们平常编译的代码量都不大,编译耗时很短,但在实际工程中大文件中,代码量动辄就是百万行量级的,此时编译就需要很长时间。故make会根据一定的检查来决定是否需要重新执行依赖关系对源文件重新进行编译。
那么这个检查是否可以重新编译的依据就是文件的时间属性。
即:如果我们更改了源文件,目录中若还有该源文件在更改前曾生成过的可执行文件,那么源文件的最近修改时间一定会比可执行文件要新。
进一步说明前先补充一下关于文件时间的相关知识:
文件时间可分为三类:
- Access:访问文件的时间
- Modify:修改文件内容的时间
- Change:修改文件属性的时间
可用stat
指令查看文件的这三类时间:
可以发现,源文件的三类时间都比可执行文件要早,或者说比可执行文件更旧(其实主要比较的是修改时间),所以无法重新进行编译。
当我们用touch
指令将源文件时间属性全都更改至最新时间后,此时源文件的时间就比可执行文件的时间更新了,就可以通过make重新编译了:
如果我想无论什么情况都能执行相应的依赖关系下的依赖方法呢?
可以通过伪目标关键字.PHONY
实现,这个关键字一般用于clean等用于做清理工作的依赖关系,如下:
.PHONY:clean
clean:
rm -rf testcode
那么最后总结一下make在用于自动化编译的原理:
(1)make会先在当前目录下找名字叫“Makefile”或“makefile”的文件。
(2)如果存在对应文章,它接着会在文件中找对应make指令后的依赖关系,若省略则默认是第一个依赖关系,并把这个依赖关系作为最终的目标文件,在上面的例子中就是testcode
。
(3)如果文件testcode
(最终目标文件)不存在,或是其依赖对象即冒号后面的文件的文件修改时间属性要比其文件新(即代表源代码最近修改过,需重新生成可执行程序),那么,他就会执行后面所定义的命令来生成testcode
这个文件。
(4)如果testcode
所依赖的文件不存在,那么make会在当前文件中找目标为该依赖文件的依赖对象,如果找到,则再根据那一个规则生成对应的文件。(如上面的从.c到.i到.s再到.o文件,这有点像一个函数跳转建立堆栈的过程)。这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
(5)在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到或依赖方法中命令错误、源文件编译不成功等,那么make就会直接退出,并报错;
本章完。
看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或看不懂的地方或有可优化的部分还恳请朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹