当前位置:   article > 正文

Linux - make命令 和 makefile_makefile 一个文件夹下所有文件

makefile 一个文件夹下所有文件

make命令和 makefile

 如果之前用过 vim 的话,应该会对 vim  又爱又恨吧,刚开始使用感觉非常的别扭,因为这种编写代码的方式,和在 windows 当中用图形化界面的方式编写代码的方式差别是不是很大。当你把vim 用熟悉的之后,虽然没有刚开始那么别扭了,但是当你在编写 一些大型项目的时候,vim 怎么用都不习惯。

而 makefile,就是一个可以定义一系列规则,来指定那个文件先编译,那个文件后编译,哪些文件需要重新编译,甚至进行更复杂的操作。

makefile 相对与 vim来说,他可以实现--“自动化编译”,一旦把 makefile 当中的各个文件的执行过程写好只好,只需要一个 make 命令,就可以让  由makefile 实现的各个文件的编译顺序 的整个工程 进行 完全自动的编译,这极大的提高了软件开发的效率


makefile 编写链接关系 和 链接方法 

首先要明确的是, make 是一个命名,它会默认在 本目录下寻找 名字叫做 "makefile" 或者 "Makefile" 也就是说,首字母大不大写其实是无所谓的。

一般情况下,makefile是创建在 源文件的 同级目录(当前目录)下创建的,我们用一个小例子来看 makefile和 make 的使用过程:

我们新建立一个文件夹(mk),在这个文件夹当中进行操作:

此时这个文件夹当中什么都没有,是空的,我们创建一个 text.c 文件:

 然后 在其中简单些一些代码:

 此时,就有了源代码文件,然后,我们建立的 makefile 文件要和 这个 源代码文件(text.c)文件在同级目录下

 然后我们使用 vim 打开编译 makefile 文件,在 makefile 文件当中进行以下书写

 在 " : " 的右边是 与 makefile同级目录之下的某一个 源代码文件;而在 " : " 的左边 是由右边的源代码文件 即将 生成的可执行文件(这个在外部使用make 名字之后才会生成)。

 由上述过程,我们就编译好了, text.c 这个源代码文件 由 makefile 生成可执行文件的过程。

此时我们只需要在 makefile 的同级目录下,使用 make 命令就可以在 同级目录之下生成一个 text 的可执行文件

 由上述结果可知,使用 make 之后,还会再屏幕上打印 编译过程。

我们把上述的 用 " : " 把源文件 和 对应可执行文件连接在一起的关系,叫做链接关系。关于链接关系我们下述在具体说明。


但是,上述我们只是写了 一个 链接关系,并没有指定这个源代码要使用什么样的编译方式,我们可以在 " : " 说明之后,在下面把 编译规则写上

 向上述就表示买吧 text.c 源代码文件 以 gcc 的方式编译成 text 可执行文件。我们把这个中方式叫做依赖方法。

注意:此处的依赖方法我们可以看到前面是 空格开头的,这里不能是空格分割,必须使用 tab 键进行分割:

 往后,我们先要编译 text.c 这个源代码文件的时候,就可以不用再使用 gcc 命令来编译这个文件了,直接在 上述同级目录之下,使用 make 命令就可以直接生成 text 可执行文件。(具体方式和上述演示是一样的,这里就不演示了)

 makefile 编写 删除源文件 规则

 当我们不想使用上述的 text 这个可执行文件时,我们当然可以 用 rm 删掉 这个 text 可执行文件,但是,用 rm 删除文件的风险太大,很容易写错文件名,而导致删错文件,要知道,在 linux 当中可是没有 像 windows 当中的 回收站一样的东西,在linux 当中删除某一个文件就是真的删除掉了。

所以,在 makefile 当中不能只提供 编写 源代码文件 链接关系 和 链接方法,还应该提供 删除的方式

 我们可以在 其中写一个 类型 make 的命名,向上述我就写了一个 make clean 的命令(这个命令的名字是自定义的),然后在后边写上 删除 text 这个可执行文件:

 在使用 make clean 这个命令之后,在目录当中就没有 text 可执行文件了。

 依赖关系 和 依赖方法

 如上所示:text:text.c 是一个依赖关系, gcc -o text text.c 是一个依赖方法,那么这里的 依赖关系 和 依赖方法是什么呢?

我们举一个例子:小明 和 他的爸爸,是一种依赖关系,当小明向他的爸爸要零花钱的时候,小明说:“爸,给我打100 块钱”,类似的话语,那么 小明 只有管他的爸爸叫爸,这是一种依赖关系,如果他问 他的室友的 爸爸,给他打钱,小明 和 他室友的爸爸 没有依赖关系,那么 室友的 爸爸为什么要给 小明打钱呢?

只有 小明 和 他父亲,是一种依赖关系,所以 ,小明 的爸爸 才会给他打钱;

但是,确定依赖关系之后,已经确定是要打钱的了,但是,如何打钱,微信还是支付宝,还是 打 银行卡,就要 有人去确定,所以这时候 就有了打钱的方法,也就是依赖方法

 那么,换到 makefile 当中也是一样的,text 和 text.c 是一种依赖关系,只有通过 text.c 才能生成 text 可执行文件,对于如何生成可执行文件,如上述:使用 gcc -o 的方式生成可执行文件。

 当前的 text 依赖的是 text.c 这个一个源文件来生成的,如果有 两个,那么后面就跟两个,三个就跟三个,是依靠 这 text.c 一个源文件 生成的 text 可执行文件(目标文件)。所以,就告诉 makefile, 目标文件是 text,这个text 依赖的文件是 text.c。但是光有依赖关系是不够的,makefile还需要知道如何根据 text.c 文件生成 text 目标文件,他也是需要知道的,所以,才有了 依赖方法

关于make 指令

上述是有 text.c 文件一步到位生成的 text 可执行文件,我们还可以写得更复杂一点,把 c 源代码文件 生成可执行文件的整个过程都是 意义列举出来:
 

 此时 make 命令打印:

 而且,发现,此时在文件夹当中不止有 text 可执行文件了,还有 上述生成的所有的中间文件:

 其实是因为,在上述当中,我们要想 从 text.o 文件 生成 text 可执行文件,makefile 就要从当前目录之下来寻找 text.o 文件,但是此时目录当中是没有 text.o 文件的,所以他就会接着走链接关系,发现 text.o 是由 text.s 生成的,但是此时目录当中还是有 text.s,他又会继续寻找链接关系,发现是 text.i 生成的 text.s········一次类推。

最后找到 text.c ,生成了text.i ,然后逆向的一直生成到 最终的 目标文件 text 可执行文件

也就是说,makefile 的最终目的是要生成 目标文件,而 目标文件的依赖关系 的 源文件,他会先去看目录当中有没有,有就直接生成,没有的话,就会去寻找 这个号源文件的依赖关系,然后递归式的 去生成 目标文件的 依赖文件。 

这个特征(过程),就特别像一个函数递归的过程。而上述的目标文件就是这个递归的 出口(结束条件),整个的结构,在保存这些依赖关系的时候,是一种栈式的结构。

 而且,上述过程,不是需要按照 顺序来 书写 链接关系的,就算是乱序,都是可以 编译出来的

make 指令:

当然,虽然能 打乱顺序,但是不能缺胳膊少腿,一个工程当中的文件编译顺序,缺一个他肯定是不能自动推导的
 

 输出:

 总结:make 命令 可以自动推导 makefile 的链接关系,就算其中的顺序是打乱的,只要存在,就会自动推导。其中的依赖关系是利用栈式的结构进行存储的。

 在上述你也发现了,我们的 clean 指令,写的是删除了4 个文件,当我们在外部输入 make clean 的时候,这个 4 个文件就都会删除,非常的方便


注意make 的默认动作是 ,把 makefile 当中的第一个目标文件作为 make 的默认动作

比如上述,把 clean 放到 text 目标文件的第一行的话,那么我们使用 make 指令是不会执行 text 的,而是执行 clean:
 

 使用 make 指令:
 

 如上述所示,我们直接使用 make 指令,直接执行的是 clean 指令,而不是 生成 text 可执行文件。

而 make + 目标文件名(如上述的 make clean),就是执行 在makefile当中的 目标文件。


在 本目录下 有 make + 目标文件  的 目标文件时,就不能再次 make 了:

 发现,当目录当中没有目标文件的时候,可以用 make 编译,但是当有目标文件的时候,就不能再次编译了但是,当上述情况发生之后,我们又去修改了 text.c 的代码,发现make 有可以进行编译了

因为,如果在第一次编译之后,源代码没有进行修改,那么是没有必要进行再次编译的(提升效率)。

我们在 ,windows 当中编写代码的时候,有时候报错了,但是我们已经进行修改了,但是修改之后还是报错,当我们重新生成解决方案之后,程序又好了,在期间我们没有修改过代码。其实原因就和上述是一样的,有些编译器会帮我们去 辨别 当前代码有没有被修改过,如果没有,那么也就没有再次编译的必要了,但是有的时候,可能编译器判断不是很灵敏,需要我们手动编译一下。


那,上述的 不给继续编译,和 判断源代码文件是否进行修改是如何做到呢

  我们都知道,可执行文件 是 在源文件的基础之上生成的,先有源文件,才有可执行文件,所以,一般而言,源文件的最近一次修改时间 是要比 可执行文件的。

 如果,我们修改了源文件,此时在目录当中还是有 可执行文件的,那么源文件的实现一定要比可执行文件的

 所以,只需要比较 可执行文件 的最近修改时间 和 源文件的最近修改时间:

  • 如果.exe 新于 .c 说明源文件是老的,不需要重新编译。
  • 如果.exe 于旧 .c 说明源文件是新的,需要重新编译。

对于 可执行 文件 和 源文件的 修改时间,一般是不会一样的,除非是用一些修改文件时间的命令。 

 stat命令(验证make 利用 文件时间 差异 实现 新老文件判断)(文件的三个时间)

 stat命令 用于访问某一个文件的 时间问题,那么,可以使用 stat命令 来查看 源文件和 可执行文件的一些时间:

  • 最近访问时间就是访问了这个文件的最近一次时间,比如 cat ,vim 都算是访问了这个文件。不管是你要 查看 还是 修改这个文件当中的内容,都是要访问这个文件的,所以 最近访问时间,几乎是 你 对这个文件任何操作的 最近时间。这个 时间 更改的频率是非常高的。
  •  文件内容修改时间,就是文件当中存储的内容,最近一次的修改时间。
  • 文件属性改变时间,就是文件当中存储的文件的属性,最近一次的修改时间。

 上述的三个时间可能不是割裂的,比如 修改了文件内容,不仅是 Modify 会改变,可能 Access 也会改变,系统可能判定为当前一次操作就是 一个 文件的访问操作,而且修改文件的内容 可能 会修改到 文件属性,所以,都不是割裂的。

 现在,我们修改一下文件的属性:

发现文件的 Change 时间已经被修改了。 

 此时 Modif 和 Access 都没有被修改,说明在当前系统下,认为 chmod 命令不算做是一次文件的访问。

其实在早期的linux 当中 access 在上述情况是要被修改的,但是也正是 Access 被修改的频率太高了,所以,如果有多个用户在修改访问这个文件,那么 Access 都要被修改,而 Access 被修改是要写进日志的,文件还是在磁盘上保存的,要修改 磁盘当中 Access 被修改的日志的话,就是要频繁的修改磁盘当中的内容 ,这效率就太低了。

所以,现在就减少了Access 修改的次数,来变相修改操作系统的效率。

如果实在想要 修改这个文件的当中的所有的时间,可以使用 touch  (要修改的文件)文件名 来实现。touch +  目录当中不存在的文件名,就是创建一个文件,如果 touch +  目录当中已经有的文件,就是更新这个文件的 所以时间。


make 命令 就是把 两个文件 各种各样的时间,转化成时间戳,然后按照时间戳的大小,来比较谁新谁旧。

验证:

首先我们先用 make 编译一次,打印出 源文件时间 和 可执行文件的文件内容时间:
 

 如上所示,我们进行第二次编译是不行的,此时 两个文件的时间如上所示:
现在,我们使用 touch 命令给 源文件更新时间,使得 源文件的时间 比 可执行文件的时间要新:

此时就可以进行 make 编译了。 


 makefile 当中的 .PHONY: 的使用

make 指令 会根据源文件 和 目标文件 的新旧,判定是否需要重新执行依赖关系,进行编译。编译不总是执行的。

 如果想要,在不能编译的情况下一定编译的话,也就是让对应的依赖关系总是被执行就可以在 makefile当中操作

 我们把 .PHONY 修饰的 目标文件称之为 伪目标

 我们不建议把 你需要的 生产的 目标文件修饰为 伪目标,因为 有些编译器不是很友好,重新编译的时候 可能不是删掉原本的可执行文件,然后重新生成新的可执行文件,可能是 不删除直接新增一个新的可执行文件。可能就会导致老的问题依旧还有。

 我们一把 把上述写的 清理操作,比如 clean ,写成 伪目标。

 makefile当中的特殊符号

 在链接方法当中的:

  • "$@" : 代表目标文件。如上述的 text
  • "$^" : 代表 : 右侧所属内容。如上述的 text.c

 在 链接方法前 加  “@” 符号可以让这个目标文件在生成的时候,不答应生成过程

make/makefile 当中伪目标文件 

 在make 的 makefile 当中,默认是自顶向下来查找可以运行目标文件的,如果遇到,那么下面的 目标文件就不会执行了。

所以,为了能 make 一次就 生成两个目标文件,因为是自顶向下搜索,所以,可以在开头处,定义 一个 伪目标文件

上述的 all 就是伪目标文件,make 自顶向下 先搜索到 all ,就要生成 all 这个伪目标文件,然后,all 又 依赖与 otherExe 和  mycommand 这两个目标文件,所以,要生成 all 目标文件,就要先生成   otherExe 和  mycommand 这两个目标文件 。

这就是 make makefile 同时生成多个 目标文件的步骤。

makefile 一些小问题(注意)

  1. makefile文件中,保存了编译器和链接器的参数选项,并且描述了所有源文件之间的关系。make程序会读取makefile文件中的数据,然后根据规则调用编译器,汇编器,链接器产生最后的输出。根据makefile的功能理解。
  2. Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释, 
  • 显式规则说明了,如何生成一个或多个目标文件。
  • make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写makefile,比如源文件与目标文件之间的时间关系判断之类
  • 在makefile中可以定义变量,当makefile被执行时,其中的变量都会被扩展到相应的引用位置上,通常使用 $(var) 表示引用变量
  • 文件指示。包含在一个makefile中引用另一个makefile,类似C语言中的include; 
  • 注释,makefile中可以使用 # 在行首表示行注释
  1. 默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件。
  • make的执行规则是,只生成所有目标对象中的第一个,当然make会根据语法规则,递归生成第一个目标对象的所有依赖对象后再回头生成第一个目标对象,生成后退出。
  • make在执行makefile规则中,根据语法规则,会分析目标对象与依赖对象的时间信息,判断是否在上一次生成后,源文件发生了修改,若发生了修改才需要重新生成。
  • makefile中的伪对象表示对象名称并不代表真正的文件名,与实际存在的同名文件没有相互关系,因此伪对象不管同名目标文件是否存在都会执行对应的生成指令。伪对象的作用有两个,1. 使目标对象无论如何都要重新生成。
  • 2. 并不生成目标文件,而是为了执行一些指令。 、
  • makefile中使用 .PHONY 来声明伪对象, .PHONY: clean。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/965571
推荐阅读
相关标签
  

闽ICP备14008679号