【Liunx篇】基础开发工具-自动化构建-make/Makefile

发布于:2024-12-22 ⋅ 阅读:(41) ⋅ 点赞:(0)

在这里插入图片描述

前言:

 上一章节我们已经了解到了编译器gcc/g++以及动静态库的相关知识,在本章里我将给大家介绍make/Makefile。平时我们在代码编译的时候采用的是gcc/g++来编译,我们编译一个源文件总是要写gcc编译命令,而且每次编译都需要我们去写,这样去写就大大的降低了我们的效率,能不能自动地将我们的代码编译形成可执行呢?答案是可以的,下来我将给大家来介绍自动化构建-make/Makefile

一.什么是make/makefile

 首先make是一个命令,makefile是一个文件。

话不多说,首先来给大家做一段演示:

  1. 创建一个test.c的文件
    在这里插入图片描述

  2. 向文件里面写入一段代码
    在这里插入图片描述

  3. test.c的同级目录下创建一个名叫Makefile/makefile的文件
    在这里插入图片描述

  4. Makefile中写入这段代码
    在这里插入图片描述
    其中第一行的mytest表示最终形成的可执行程序,它依赖的叫做test.c,即当前目录下的test.c文件。第二行必须紧挨着第一行,gcc test.c -o mytest,表示形成可执行程序的方法。保存并退出。

  5. 我们只需make一下就能形成可执行程序
    在这里插入图片描述
    在这里插入图片描述
    我们会发现在当前目录下会自动形成mytest

  6. 我们输入./mytest就可以运行mytest
    在这里插入图片描述

以上操作就叫做makeMakefile

二.make/makefile的核心思想

mytest:test.c
 gcc test.c -o mytest

依赖关系:

  • 上面的文件mytest,它依赖test.c

依赖方法:

  • gcc test.c -o mytest

在这里插入图片描述
所以make/Makefile的核心思想是:依赖关系依赖方法形成目标文件
依赖关系和依赖方法必须同时存才有效。

三.make/makefile的具体语法

能够把可执行程序执行,那么也应能把可执行程序删除,我们再来一段演示:

  1. Makefile中添加以下内容,保存并退出
    在这里插入图片描述
    其中第4行.PHONY是Makefile当中的一个语法,.PHONY后面表示的是一个可修饰符号,比如说clean。第5行clean也是个依赖关系,只不过它的依赖列表为空,表示clean谁都不依赖。第6行依赖方法rm -f mytest表示删掉mytest
  2. 编译项目-make,形成可执行程序
    在这里插入图片描述
  3. 执行可执行程序-./mytest
    在这里插入图片描述
  4. 清理掉这个项目-make clean
    在这里插入图片描述

我们就完成了能够构建和能够清理的Makefile
在这里插入图片描述

  • make会自顶向下扫描makefile中的文件,如果想指定形成,就需要make + 目标文件的名称

伪目标 .PHONY
 当我们第一次编译好mytest,后面我们再想编译它却不允许我们编译了,这是为什么呢?
在这里插入图片描述

原因是我们的test.c未进行任何的修改,也就是说mytest依赖的源文件没有进行任何的修改,那么就没有重新编的必要了。
我们再来一段演示:

  1. 我们让.PHONY修饰以下目标文件mytest
    在这里插入图片描述
  2. 我们查看代码时从上向下扫描,第一个遇到的就是.PHONY:mytest
    在这里插入图片描述
  3. make一下
    在这里插入图片描述
    所以我们.PHONY修饰的目标文件代表的含义是:所依赖的方法总是被执行的

在这里插入图片描述
为什么上面这个程序只能被执行一次,如果我们把它里面的内容修改一下它就又让我们编译了,这是怎么做到的呢?
对比源文件和可执行文件的修改时间,如果源文件最近修改的时间要比可执行程序文件的说明源文件被修改过了,就能再次进行编译。如果源文件最近修改的时间要比可执行程序文件的,说明可执行程序文件的比较新,所以不能再次编译。那么问题又来了,这个时间是如何定义的呢?答案是acm时间。
在这里插入图片描述
以前说过文件 = 文件内容 + 文件属性,我们如果更改了文件的内容,它的Modify时间就会变化,如果只改变文件的属性,它的Change时间就会发生变化。
所以:

  • Modify:文件内容更改的时间
  • Change:文件属性更改的时间
  • Access:文件的最近访问时间

演示:

  1. 未改变test.c文件内容之前的Modify时间
    在这里插入图片描述

  2. test.c文件里面增加点内容后的Modify时间
    在这里插入图片描述
    有人可能会问Modify时间确实变了,但是Change时间也发生了变化,原因是往文件里面增添了内容,文件的大小也会发生改变,所以文件的属性就变了,Change时间就变了。

💦详谈Access 时间
Access时间是文件的最近访问时间,也就是读取文件的内容读取的时间。早些时候的Liunx内核,当你去查看这个文件的时候,不修改,也就是打开这个文件,那么这个Access时间就会随着你的打开时间进行更新,后来人们就发现这种特性非常的不好,因为大多数时候我们都是在查看文件,修改和重新编写文件的次数很少,每次查看都要修改这个文件的Access时间,就会在系统层面上带来大量的io,这样就增加系统io的压力,所以新的内核就说了,除非改变ModifyChange才会更改Access时间,如果要访问这个文件,不再是每访问一次更新一次,而是根据访问的特定次数,比如访问10次后就更新一次。有效的减少了io次数。

所以如何判定可执行程序与源文件谁新谁旧呢?
对比源文件和可执行程序的Modify时间

验证:

  1. test.c的原本时间
    在这里插入图片描述
  2. touch test.c
    在这里插入图片描述
    我们会发现tets.c的所有时间已经被更新到最新了,touch不仅能创建新的文件,还能更新一个已经存在的文件的时间。
  3. make一下形成目标文件mytest
    在这里插入图片描述
  4. 我们会发现Mytest的Modify时间要比test.c文件的要新,所以它就不允许我们再次编译
    在这里插入图片描述
  5. 根据我们推到的原则,test.c的Modify时间要是比mytest的时间新,他就让我们再次编译,我们更新一下test.c文件的Modify时间
    在这里插入图片描述
  6. 此时的test.c文件的Modify时间是更新的,所以这时候再make就会让我们编译
    在这里插入图片描述
  7. 再次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

  1. 最终版本第一代
    在这里插入图片描述

  2. make
    在这里插入图片描述

  3. 形成可执行程序mytest
    在这里插入图片描述

  4. 运行mytest
    在这里插入图片描述

  5. 清理
    在这里插入图片描述

这样写Makefile也不具有通用性

  1. 第二代,我们将最终形成的可执行程序称之为BIN
    在这里插入图片描述
    这个叫做Makefile中的变量,你可以给你形成的mytest定义一个变量名

  2. SRC表示形成可执行程序依赖的源文件
    在这里插入图片描述

  3. OBJ表示依赖的目标文件
    在这里插入图片描述

  4. CC表示我们要用到的编译器
    在这里插入图片描述

  5. RM表示我们要用到的删除命令
    在这里插入图片描述

  6. 紧接着我们的Makefile就不用上面那样写了

  7. $(BIN)访问圆括号内的内容,即访问BIN,而BIN形成的目标程序依赖的是OBJ,即依赖的是.o文件
    在这里插入图片描述

  8. 编译时使用的gcc方法
    在这里插入图片描述
    即把所有的文件用与之对应的变量名替换即可

  9. 替换完后是这样子
    在这里插入图片描述
    所以未来如果想要更改源文件,或者可执行程序,只需要更改前5行的内容即可,下面的就不用动了

这样的方法也还不是特别的通用
✏️比如说:

  1. 我们有一百多个源文件
    在这里插入图片描述
  2. makefile从上扫描,它会首先执行第一个遇到的文件,SRC依赖的不只是test.c,而是依赖所有的.c,那么如何动态获取所有的.c呢?
    第一种:在这里插入图片描述
    表示把当前所有的.c文件罗列出来,罗列出来后全部放进SRC
    第二种:makefile提供了一个基本的函数wildcard,其实与上面是等价的
    在这里插入图片描述
  3. 我们要把所有的源文件全部更换成同名.o,再把所有的.o链接形成可执行
    在这里插入图片描述
    表示把SRC里面的.c全部替换成.o形成OBJ
  4. 正常的代码编写
    在这里插入图片描述
    其中$^表示的是上面依赖关系中,所有的依赖文件列表(OBJ)。$@表示所形成的目标文件(BIN)
  5. 用所有的.c形成.o
    在这里插入图片描述
    其中%叫做通配符,表示匹配任意内容,%.c表示匹配任意以.c结尾的文件。$<表示把展开的多个.c一个一个交给该命令,经过该方法加工成.o
  6. make
    在这里插入图片描述
  7. 清理
    在这里插入图片描述
    在这里插入图片描述
    这就是更加通用的makefile

补充几点小知识:

  1. 编译器在执行我们对应的命令时,会在屏幕上将执行的对应命令回显在屏幕上,我们如果不需要回显,就仅需在命令前加个@符号即可。
    在这里插入图片描述
  2. 依赖方法不止可 以写一行,我们也可以定制化输出信息
    在这里插入图片描述
    在这里插入图片描述

总结:

makefile是一个自动化构建的工具,可以通过定义规则、变量和函数来简化编译过程。在软件开发中,掌握Makefile的使用对于提高开发效率和维护项目的可构建性具有重要意义。


今天的内容就分享到这里,如果这篇文章对你有帮助,记得点赞,评论+收藏 ,最后别忘了关注作者,作者将带领你探索更多关于Liunx方面的问题。