Linus - make命令 和 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

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

相关推荐
咕噜Yuki060923 分钟前
博睿谷IT认证-订阅试学习
linux·学习·redhat·rhce·红帽认证·ocm认证
EPSDA26 分钟前
Java的IO流(二)
java·开发语言
小灰灰爱代码29 分钟前
C++——将数组a[5]={-1,2,9,-5,7}中小于0的元素置成0。并将其结果输出(要求:用数组名作为函数的参数来实现)
数据结构·c++·算法
冰红茶兑滴水29 分钟前
Linux 线程互斥
linux·c++
vitobo1 小时前
Java的JDBC编程
java·开发语言
呆萌小新@渊洁1 小时前
后端接收数组,集合类数据
android·java·开发语言
元素之窗2 小时前
如何在 CentOS 中管理用户、组和服务状态
linux·运维·centos
爱吃烤鸡翅的酸菜鱼2 小时前
java(3)数组的定义与使用
java·开发语言·idea·intellij idea
kingbal3 小时前
SpringBoot:解析excel
java·开发语言·excel
咩咩大主教3 小时前
Linux下的简单TCP客户端和服务器
linux·服务器·c语言·开发语言·c++·tcp/ip·网络编程