1. Makefile/makefile工具
首先展示一下,makefile工具如何使用。我们先写一个C语言程序
然后我们建立一个Makefile/makefile文件,m大小写均可。我们在文件中写入这样两行
wq保存退出后,我们使用 make 命令
可以看到生成了可执行程序,其效果就是我们刚才写的打印出 hello world
这就是makefile的作用,一个工程中的源文件不计其数,其按类型、功能、模块分放在若干给文件当中,而makefile定义了一系列的规则来指定,哪些文件需要先编译,那些文件需要后编译,和其他一些操作。它是一个“自动化编译”的效果,一旦写好makefile文件,我们就无需自己手动一个一个的敲编译命令,至于要一个make就可以使整个工程完全自动编译起来。
1.1 make语法
下面我们就来拆解一下makefile文件的语法。
第一行冒号前的部分我们叫目标文件,可以通过make调用目标文件来达到执行下面依赖方法的效果。
冒号后面的部分我们叫依赖文件列表,这个列表中可以放多个依赖文件,用于提供给下面的依赖方法使用,就是说依赖方法要依赖这个列表中的文件去执行某些操作。
第二行我们写依赖方法,依赖方法必须以tab开头。
目标文件和依赖文件列表的集合称为依赖关系,makefile本质是依赖关系和依赖方法的集合。
事实上,makefile文件的作用是执行事先使用依赖方法写好的一些命令:
可以看到 make+目标文件 就可以很好的完成我们预设定好的命令,但是它在执行命令的时候都会把命令打印一边,如果不想看见打印命令的话,可以在命令前加上@符号就可以
这里可以发现我在执行第一行编译的时候并没有加上目标文件,但是在执行第四行和第九行的时候都加上了目标文件。
这是因为makefile文件,会被make从上到下扫描,第一个目标名,是对于make来说属于可以缺省形成的,也就是说如果make后面什么都不跟,就默认执行第一个目标文件。也就是说,谁放在最开头,谁就可以直接make执行不需要加目标文件名。
1.2 make特性
1.2.1 执行中断
我们进入代码文件中,故意写出点语法错误,这样编译的话就会导致报错,然后我们再对makefile文件也稍加修改,让它在编译完成之后打印点东西出来
make执行一下编译命令。
可以看到,在编译报错之后直接跳出了makefile文件,执行被中断 aaa 并没有被打印出来。
1.2.2 更新编译 与 stat命令
ps:其实这个更新编译的特性属于make指令和gcc编译指令结合后出现的特性,单独使用gcc编译指令是不会出现这一特性的。
其实我前面写的makefile文件是有问题的,在编译的依赖方法中,实际上是hello文件在依赖hello.c文件,但是我在目标文件中声明的文件却是proc文件作为目标文件在依赖helloc.c文件。如果按我上面这种错误的语法写makefile的话是不会出现更新编译的特性的。
因此我们先把makefile文件改成正确的语法
我们clean清理一下,然后make多次,看看现象。
可以看到只成功make了一次,之后再make就表示hello已经是最新了,不再编译。
这拒绝编译的反应是符合道理的,因为如果一个文件已经是最新了就没必要再重复编译,如果一个文件很大,那重复编译的行为就会浪费掉大量的时间成本。
其拒绝编译的逻辑就是对比目标文件和依赖文件的更新时间,如果依赖文件的更新时间更早说明目前的目标文件是最新状态,如果依赖文件更新时间更晚,说明hello.c文件已经被更改过了,目标文件也就需要重新编译了。
这个文件的属性我们可以用 stat 命令来查看。
其中时间属性就是我圈出来的这三个时间,第一个Access我们称为Atime表示最近文件被访问的时间,它比较没用,更新编译也不看这个时间。它这里面的机制比较奇怪不是说访问一次就一定会记录下来,而是访问到了一定次数才会记录一次,主要是为了防止操作系统要频繁的修改文件的访问时间属性。
第二个Modify我们称为Mtime表示文件内容最近被修改的时间,第三个Change我们称为Ctime表示文件属性最近被修改的时间。
修改文件内容可能会牵连到文件属性的修改,比如文件的大小。
更新编译的逻辑就是根据Mtime进行对比的,如果.c文件修改时间早于目标文件,说明目标文件已经是最新的了,无需编译;如果.c文件修改时间晚于目标文件,说明.c文件被更新过了,目标文件也需编译更新。
1.2.3 .PHONY 设置伪目标
如果我们想让这个编译指令忽略掉更新限制,我们可以选择将设置伪目标,语法如下
这个语法的意思就是将hello文件变成了一个伪目标,也就是说告诉makefile hello 是一个不存在的文件,此时makefile就不会去特意对比hello和hello.c文件的更新时间,而是直接执行gcc编译指令了。
这个语法就和我一开始所写的proc目标文件错误有异曲同工之处,它们都是不存在的伪目标,只不过一种是刻意用 .PHONY 指定出来的,一种是真的不存在。
可以验证一下,将hello设置成伪目标之后是不是可以忽略最新编译限制去make了
也可以将目标文件搞成proc一个真正不存在的目标试试,也是同样的效果。
1.2.4 入栈执行
我们可以在makefile文件中如下写法,将上节中编译的整个过程写出来
然后我们保存退出,试试这样make的效果
我们可以通过这里看出,当makefile发现目录的明面上没有hello.o文件,那它就会将依赖方法入栈,然后直到找到出口回归,将栈中所有依赖方法执行。
1.2 make进阶语法
1.2.1 %通配符
我们在实际使用的时候不会像上面那样把一个文件的编译过程弄的那么复杂,而是会面临一次性想要编译多个文件的需求。
处理这样的问题可以是使用makefile中类似通配符的东西 %
由makefile自己推导所需的.c文件,将它们一个一个的交给gcc -c指令,并生成对应的.o文件,其效果我们可以make看一下
1.2.2 =变量定义
makefile中还可以定义和使用变量,用作设定文件名。
定义时等号左右不能有空格,使用变量时用 $(变量) 将变量包起来,这样makefile文件就会将定义好的内容替换到下面的各个位置。
1.2.3 $符号的使用
我们前面说过 $< 的含义是将依赖列表中的文件一个一个的交给指令。
下面还有 $^ $@ 两个指令
其中 $^ 的含义就是将依赖列表中的所有文件一次性交给指令, $@ 的含义是将 : 左侧也就是想要形成的目标文件。
这种操作就像是在宏替换一样。这样写完后我们就可以通过一份 hello.c 文件生成多个任意名称的文件,只需要修改bin设定的值即可。
1.2.4 一次性形成多个可执行程序
现在我们想要一次性把 hello.c hello1.c 都形成可执行程序改怎么做
事实上我们可以使用伪目标的依赖列表来巧妙的完成这一需求