文章目录
前言:
上一章节我们已经了解到了编译器gcc/g++以及动静态库的相关知识,在本章里我将给大家介绍make/Makefile。平时我们在代码编译的时候采用的是gcc/g++来编译,我们编译一个源文件总是要写gcc编译命令,而且每次编译都需要我们去写,这样去写就大大的降低了我们的效率,能不能自动地将我们的代码编译形成可执行呢?答案是可以的,下来我将给大家来介绍自动化构建-make/Makefile。
一.什么是make/makefile
首先make是一个命令,makefile是一个文件。
话不多说,首先来给大家做一段演示:
-
创建一个
test.c
的文件
-
向文件里面写入一段代码
-
在
test.c
的同级目录下创建一个名叫Makefile/makefile
的文件
-
向
Makefile
中写入这段代码
其中第一行的
mytest
表示最终形成的可执行程序,它依赖的叫做test.c
,即当前目录下的test.c
文件。第二行必须紧挨着第一行,gcc test.c -o mytest
,表示形成可执行程序的方法。保存并退出。 -
我们只需
make
一下就能形成可执行程序
我们会发现在当前目录下会自动形成
mytest
-
我们输入
./mytest
就可以运行mytest
了
以上操作就叫做make和Makefile
二.make/makefile的核心思想
cpp
mytest:test.c
gcc test.c -o mytest
依赖关系:
- 上面的文件
mytest
,它依赖test.c
依赖方法:
gcc test.c -o mytest
所以make/Makefile
的核心思想是:依赖关系 和依赖方法 形成目标文件
依赖关系和依赖方法必须同时存才有效。
三.make/makefile的具体语法
能够把可执行程序执行,那么也应能把可执行程序删除,我们再来一段演示:
- 在
Makefile
中添加以下内容,保存并退出
其中第4行.PHONY
是Makefile当中的一个语法,.PHONY
后面表示的是一个可修饰符号,比如说clean
。第5行clean
也是个依赖关系,只不过它的依赖列表为空,表示clean
谁都不依赖。第6行依赖方法rm -f mytest
表示删掉mytest
。 - 编译项目-
make
,形成可执行程序
- 执行可执行程序-
./mytest
- 清理掉这个项目-
make clean
我们就完成了能够构建和能够清理的Makefile
- make会自顶向下扫描makefile中的文件,如果想指定形成,就需要
make + 目标文件的名称
伪目标 .PHONY
当我们第一次编译好mytest
,后面我们再想编译它却不允许我们编译了,这是为什么呢?
原因是我们的test.c
未进行任何的修改,也就是说mytest
依赖的源文件没有进行任何的修改,那么就没有重新编的必要了。
我们再来一段演示:
- 我们让
.PHONY
修饰以下目标文件mytest
- 我们查看代码时从上向下扫描,第一个遇到的就是
.PHONY:mytest
make
一下
所以我们.PHONY
修饰的目标文件代表的含义是:所依赖的方法总是被执行的
为什么上面这个程序只能被执行一次,如果我们把它里面的内容修改一下它就又让我们编译了,这是怎么做到的呢?
对比源文件和可执行文件的修改时间 ,如果源文件最近修改的时间要比可执行程序文件的新说明源文件被修改过了,就能再次进行编译。如果源文件最近修改的时间要比可执行程序文件的老,说明可执行程序文件的比较新,所以不能再次编译。那么问题又来了,这个时间是如何定义的呢?答案是acm
时间。
以前说过文件 = 文件内容 + 文件属性 ,我们如果更改了文件的内容,它的Modify
时间就会变化,如果只改变文件的属性,它的Change
时间就会发生变化。
所以:
Modify
:文件内容更改的时间Change
:文件属性更改的时间Access
:文件的最近访问时间
演示:
-
未改变
test.c
文件内容之前的Modify
时间
-
向
test.c
文件里面增加点内容后的Modify
时间
有人可能会问
Modify
时间确实变了,但是Change
时间也发生了变化,原因是往文件里面增添了内容,文件的大小也会发生改变,所以文件的属性就变了,Change
时间就变了。
💦详谈Access
时间
Access
时间是文件的最近访问时间,也就是读取文件的内容读取的时间。早些时候的Liunx内核,当你去查看这个文件的时候,不修改,也就是打开这个文件,那么这个Access
时间就会随着你的打开时间进行更新,后来人们就发现这种特性非常的不好,因为大多数时候我们都是在查看文件,修改和重新编写文件的次数很少,每次查看都要修改这个文件的Access
时间,就会在系统层面上带来大量的io
,这样就增加系统io
的压力,所以新的内核就说了,除非改变Modify
和Change
才会更改Access
时间,如果要访问这个文件,不再是每访问一次更新一次,而是根据访问的特定次数,比如访问10次后就更新一次。有效的减少了io
次数。
所以如何判定可执行程序与源文件谁新谁旧呢?
对比源文件和可执行程序的Modify
时间
验证:
test.c
的原本时间
touch test.c
我们会发现tets.c
的所有时间已经被更新到最新了,touch
不仅能创建新的文件,还能更新一个已经存在的文件的时间。make
一下形成目标文件mytest
- 我们会发现
Mytest
的Modify时间要比test.c
文件的要新,所以它就不允许我们再次编译
- 根据我们推到的原则,
test.c
的Modify时间要是比mytest
的时间新,他就让我们再次编译,我们更新一下test.c
文件的Modify时间
- 此时的
test.c
文件的Modify时间是更新的,所以这时候再make就会让我们编译
- 再次make一下又不让编了
结论: 要不要重新编译取决于源文件和可执行程序文件的最近修改时间。
💦重谈.PHONY
.PHONY
修饰一个符号表示它为为目标,作用:总是被执行的。
如何做到的呢?在gcc
这里是忽略对比时间 ,.PHONY
下面的这条命令总是被执行。
四.进一步理解make/makefile的具体语法
下面我们就来写一个较为完整的Makefile
Makefile自顶向下运行,mytest依赖test.o,当运行到gcc test.o -o mytest
时就会发现当前目录没有test.o文件,所以Makefile发现.o不存在,Makefile会自动去找test.o依赖谁,test.o依赖test.s,test.s在当前目录也不存在,.s
依赖.i
,.i
依赖.c
,.c
在当前目录下存在,所以用.c
对应的方法形成.i
,.i
形成了.s
就有了,.s
有了.o
就有了,.o
有了最终的可执行程序就有了。
📌结论:
- make会进行依赖关系的推导,知道依赖文件时存在的
如何推导呢?
推导原则:将依赖方法不断入栈,推导完毕,出栈执行方法
更加具有通用型的Makefile
-
最终版本第一代
-
make
-
形成可执行程序
mytest
-
运行
mytest
-
清理
这样写Makefile也不具有通用性
-
第二代,我们将最终形成的可执行程序称之为
BIN
这个叫做Makefile中的变量,你可以给你形成的
mytest
定义一个变量名 -
SRC
表示形成可执行程序依赖的源文件
-
OBJ
表示依赖的目标文件
-
CC
表示我们要用到的编译器
-
RM
表示我们要用到的删除命令
-
紧接着我们的
Makefile
就不用上面那样写了 -
$(BIN)
访问圆括号内的内容,即访问BIN
,而BIN
形成的目标程序依赖的是OBJ
,即依赖的是.o
文件
-
编译时使用的
gcc
方法
即把所有的文件用与之对应的变量名替换即可
-
替换完后是这样子
所以未来如果想要更改源文件,或者可执行程序,只需要更改前5行的内容即可,下面的就不用动了
这样的方法也还不是特别的通用
✏️比如说:
- 我们有一百多个源文件
makefile
从上扫描,它会首先执行第一个遇到的文件,SRC
依赖的不只是test.c,而是依赖所有的.c
,那么如何动态获取所有的.c
呢?
第一种:
表示把当前所有的.c
文件罗列出来,罗列出来后全部放进SRC
中
第二种:makefile
提供了一个基本的函数wildcard
,其实与上面是等价的
- 我们要把所有的源文件全部更换成同名
.o
,再把所有的.o
链接形成可执行
表示把SRC
里面的.c全部替换成.o形成OBJ
- 正常的代码编写
其中$^
表示的是上面依赖关系中,所有的依赖文件列表(OBJ)。$@
表示所形成的目标文件(BIN) - 用所有的
.c
形成.o
其中%
叫做通配符,表示匹配任意内容,%.c
表示匹配任意以.c
结尾的文件。$<
表示把展开的多个.c
一个一个交给该命令,经过该方法加工成.o
。 make
- 清理
这就是更加通用的makefile
补充几点小知识:
- 编译器在执行我们对应的命令时,会在屏幕上将执行的对应命令回显在屏幕上,我们如果不需要回显,就仅需在命令前加个
@
符号即可。
- 依赖方法不止可 以写一行,我们也可以定制化输出信息
总结:
makefile是一个自动化构建的工具,可以通过定义规则、变量和函数来简化编译过程。在软件开发中,掌握Makefile的使用对于提高开发效率和维护项目的可构建性具有重要意义。
今天的内容就分享到这里,如果这篇文章对你有帮助,记得点赞,评论+收藏 ,最后别忘了关注作者,作者将带领你探索更多关于Liunx方面的问题。