这个3/4行的语法和1/2行是一样的。也是依赖关系和依赖方法。
make命令扫描makefile文件时,从上向下扫描,默认形成一个目标文件。
指定make clean
的时候才回去执行对应的清除。
为什么要给我们的clean.PHONY:clean
声明它是伪目标呢?
PHONY类似一种建议性的关键字。
伪目标,表示对应的依赖方法和依赖关系总是被执行。
什么叫不被执行?
我们上面的这组可执行程序没有被修饰,所以我们如果make完又make,是不会执行的。但如果用为目标修饰了,就会发现也可以执行。
所以我们一般可执行程序不用伪目标修饰,因为如果代码没有更新,没有必要再次编译。
默认老代码不作重新编译。
make怎么知道bin和.c的新旧?
Linux下每一个文件有三个相关时间,分别是Access/Modify/Change
文件=内容+属性
cat打印内容,ls打印属性
如果我们只改文件内容,那么Modify时间被修改。
如果我们只改文件属性,那么Change时间被修改。
如果不修改,单纯查看,Access时间被更新。
可以看到,这样我们的Modfiy时间就被改了。
而且文件属性页同步变化了,所以时间也改了:
大小和时间本就是文件的属性,所以基本都会同时修改。
更改这个权限,我们就是只改属性。
如果过于高频因为查看就更新时间,会有很多隐性的成本。所以linux现在查看上若干次才会更新一次Access时间,具体多少次看具体系统。
我们知道,myproc.c的Modify时间应当是要比myroc早的。所以make可以以此为依据判断myproc.c是否被修改过了。
我们曾经说过,touch可以用来改文件时间:
比如带上-a,就可以只改Access时间。
如果什么选项都不带,就会把ACM三个时间全部更新。
所以通过touch更改时间,也能使我们重复使用make。
所以PHONY的作用就是修饰后总是被执行,如果我们用其修饰第一组的代码,我们就可以重复使用make。
在makefile中注释代码我们使用#来注释。
makefile会在自己内部维护一个类似栈结构的东西。
makefile推导规则
从上往下扫描,从下往上执行。
当我们从上往下扫描,一条找不到时就入栈。所以就保证了我们最后找到myproc.c时,从下往上的执行顺序。
当然我们一般不会这么去写整个过程,自己增加难度。
makefile也允许我们去定义变量。所以我们写makefile一般喜欢这样写:
如果改写成这样:
就不会回显命令。
这样,BIN是我们的目标文件,CC是编译器,SRC是源文件,FLAGS是方法。我们将这组变量打印。
$(BIN)
就是取它的内容。
所以我们全以变量的形式呈现了。这就是一个基于变量版本的makefile了。
有点像宏。
所以我们也能猜出,这样做的好处也就是替换具体的文件时不用改后面的代码了,只要在变量处稍作修改。
其实,还可以写得更加优雅:
$@
代表的是目标文件,$^
代表的是依赖的众多文件列表。
我们的依赖方法来源不止一行。
我们可以把默认的显示信息用@关闭,然后写自己的信息:
但我们发现还是将echo
回显了,于是我们继续优化:
多文件呢?
而且,我们并不喜欢直接从.c到可执行文件,而是喜欢先从.c到.o,原因以前说过,为了方便去链接C语言库等,所以我们要改写。
LFLAGS的L代表link。OBJ代表.o文件。
.o文件从哪来的?所以我们要再写一组依赖关系和方法。但是一般我们的.c文件和以此生成的.o文件不止一个,为了简写,我们使用%.o:%.c
这样来写我们的依赖关系。%就像makefile模式下的通配符;在依赖关系中我们也简写为:$(CC) $(FLAGES) $<
,也是gcc -c $<
,这里<
用来简写所有的.c文件。
我们知道,gcc -c myproc.c -o myproc.o
可以帮我们从.c文件编译到.o文件。
然后,gcc -c myproc.c
则可以帮我们直接编译出同名.o文件。
这也就是为什么上面我们写成$(CC) $(FLAGS) $<
优化:
这个地方,我们可以SRC=$(shell ls *.c)
执行ls *.c
的命令,然后把对应所有的源文件放在SRC中。就不用我们一个一个手写要的源文件了。
第二种做法:
makefile天生自带类似函数的东西,可以支持我们这样写:SRC=$(wildcard *.c)
也是类似通配符。
所以我们的OBJ同样也需要这样能够使用类似通配符的效果,来避免一个一个手写。
OBJ=$(SRC:.c=.o)
这就是最终的makefile。
我们这样来创建100个文件:
不用改刚才的makefile文件了,可以直接这样使用make和make clean:
Makefile还有其他语法,日后可以再学。