如果之前用过 vim 的话,应该会对 vim 又爱又恨吧,刚开始使用感觉非常的别扭,因为这种编写代码的方式,和在 windows 当中用图形化界面的方式编写代码的方式差别是不是很大。当你把vim 用熟悉的之后,虽然没有刚开始那么别扭了,但是当你在编写 一些大型项目的时候,vim 怎么用都不习惯。
而 makefile,就是一个可以定义一系列规则,来指定那个文件先编译,那个文件后编译,哪些文件需要重新编译,甚至进行更复杂的操作。
makefile 相对与 vim来说,他可以实现--“自动化编译”,一旦把 makefile 当中的各个文件的执行过程写好只好,只需要一个 make 命令,就可以让 由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 可执行文件。(具体方式和上述演示是一样的,这里就不演示了)
当我们不想使用上述的 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 目标文件,他也是需要知道的,所以,才有了 依赖方法。
上述是有 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 当中编写代码的时候,有时候报错了,但是我们已经进行修改了,但是修改之后还是报错,当我们重新生成解决方案之后,程序又好了,在期间我们没有修改过代码。其实原因就和上述是一样的,有些编译器会帮我们去 辨别 当前代码有没有被修改过,如果没有,那么也就没有再次编译的必要了,但是有的时候,可能编译器判断不是很灵敏,需要我们手动编译一下。
那,上述的 不给继续编译,和 判断源代码文件是否进行修改是如何做到呢?
我们都知道,可执行文件 是 在源文件的基础之上生成的,先有源文件,才有可执行文件,所以,一般而言,源文件的最近一次修改时间 是要比 可执行文件要早的。
如果,我们修改了源文件,此时在目录当中还是有 可执行文件的,那么源文件的实现一定要比可执行文件的晚。
所以,只需要比较 可执行文件 的最近修改时间 和 源文件的最近修改时间:
对于 可执行 文件 和 源文件的 修改时间,一般是不会一样的,除非是用一些修改文件时间的命令。
stat命令 用于访问某一个文件的 时间问题,那么,可以使用 stat命令 来查看 源文件和 可执行文件的一些时间:
上述的三个时间可能不是割裂的,比如 修改了文件内容,不仅是 Modify 会改变,可能 Access 也会改变,系统可能判定为当前一次操作就是 一个 文件的访问操作,而且修改文件的内容 可能 会修改到 文件属性,所以,都不是割裂的。
现在,我们修改一下文件的属性:
发现文件的 Change 时间已经被修改了。
此时 Modif 和 Access 都没有被修改,说明在当前系统下,认为 chmod 命令不算做是一次文件的访问。
其实在早期的linux 当中 access 在上述情况是要被修改的,但是也正是 Access 被修改的频率太高了,所以,如果有多个用户在修改访问这个文件,那么 Access 都要被修改,而 Access 被修改是要写进日志的,文件还是在磁盘上保存的,要修改 磁盘当中 Access 被修改的日志的话,就是要频繁的修改磁盘当中的内容 ,这效率就太低了。
所以,现在就减少了Access 修改的次数,来变相修改操作系统的效率。
如果实在想要 修改这个文件的当中的所有的时间,可以使用 touch (要修改的文件)文件名 来实现。touch + 目录当中不存在的文件名,就是创建一个文件,如果 touch + 目录当中已经有的文件,就是更新这个文件的 所以时间。
make 命令 就是把 两个文件 各种各样的时间,转化成时间戳,然后按照时间戳的大小,来比较谁新谁旧。
验证:
首先我们先用 make 编译一次,打印出 源文件时间 和 可执行文件的文件内容时间:
如上所示,我们进行第二次编译是不行的,此时 两个文件的时间如上所示:
现在,我们使用 touch 命令给 源文件更新时间,使得 源文件的时间 比 可执行文件的时间要新:
此时就可以进行 make 编译了。
make 指令 会根据源文件 和 目标文件 的新旧,判定是否需要重新执行依赖关系,进行编译。编译不总是执行的。
如果想要,在不能编译的情况下一定编译的话,也就是让对应的依赖关系总是被执行,就可以在 makefile当中操作:
我们把 .PHONY 修饰的 目标文件称之为 伪目标。
我们不建议把 你需要的 生产的 目标文件修饰为 伪目标,因为 有些编译器不是很友好,重新编译的时候 可能不是删掉原本的可执行文件,然后重新生成新的可执行文件,可能是 不删除直接新增一个新的可执行文件。可能就会导致老的问题依旧还有。
我们一把 把上述写的 清理操作,比如 clean ,写成 伪目标。
在链接方法当中的:
在 链接方法前 加 “@” 符号可以让这个目标文件在生成的时候,不答应生成过程: