以后因为要真的好好搞linux编程,不得不开始学习写makefile,于是乎在边看手头工程别人写的MAKEFILE,一边看一些网上的参考资料,尤其上来就接触到了伪目标这个东西,所以一直云里雾里。好多人推荐的陈皓写的一个关于如何写MAKEFILE的资料,我也看了,可是关于伪目标的部分,clean的部分,个人认为他有些地方写的很矛盾,理解不通,于是最后找到了GNU的官方make.pdf,专门研究了研究这个部分,因为发现网上关于这个的讨论好多都是抄的他的,思路都很像,所以想要把自己的认识写出来,也好和大家一同来分享或者研究,也希望大家有困惑的地方就留言,可以一起讨论。
进入正题:相信大家既然都知道伪目标这个东西,自然不用介绍最白痴的目标、前提、配方(自己瞎起的,就是英文版recipe),规则(英文版的rule)指的是整个前面三个东西的合体,所以千万不要把配方和规则搞混了。
make在执行的时候,首先找到一个最终目标,它所能看到的第一个,自然也就是你写的MAKEFILE中写的第一个带有冒号的前面的那个东西。然后就通过这个终极目标冒号后的前提去一个个递推下去找前提的前提,一直找到最根上,然后比时间,如果前提的时间戳更新,则执行这儿的配方,然后再往之前推的那一层找,再比再执行配方,层层再上去最后回到最终目标,然后再执行终极目标下的配方。注意:整个过程就是递归,配方是在前提准备完毕后才执行的,不是在找的时候,这里当时搞得我逻辑乱死了,后来才搞明白。
所以啦,
与终极目标没关系的其他的规则,都不会执行的。
这个和它是不是伪目标毛关系都没有的!再次声明,一个规则是目标+前提+配方的合体,不是配方!!!
你如果把clean:.......写在MAKEFILE的最上面,你再make你会发现,它会执行clean下的配方(rm),然后就没有然后了,因为后面的目标和这个clean没有依赖关系啊,自然编译器就什么都不干了,这和这个目标新不新没有任何关系。也就是说什么时候才判断这个新旧呢,只有和终极目标能牵扯上关系的,而且层层判断走到它的时候才判断,其余时候都是闲着的,人编译器不会那么傻的你只要make它就都判断一遍。
现在来说说伪目标吧。所谓的伪目标,说得是只有目标,没有前提(也可以有前提),并且配方也不是能生成目标的命令(比如rm这种),或者干脆也没有配方(光秃秃的好像是没啥意义了),说白了就是编译器没办法根据你写的这个规则生成一个目标文件出来。只要是这种的,都是,都是,都是伪目标!!!
不用声明的!!!!自然我们的clean就是伪目标啦。
还说上面的那个,我们把clean:.....放到头上,然后敲make,只执行rm,这个时候如果再make一次,它就再rm一次(为了能够rm成功,在rm加个-f强制执行,否则会报错的,为啥,因为你删过了啊)。不知道大家在正常的make时候有没有连着make几次,你会发现,和这里的不同就是你再make第二次的时候,返回的结果就是”现在目标文件(你的终极目标)已经是最新的了“,并没有不停重复再来。为啥呢,因为你的正常的终极目标都是会生成文件的,咱刚才的clean,是生不成clean这个目标文件的,所以啦,每次make的时候clean是没有的,没有就得生成啊,虽然走到最后其实也生不成(感觉像是编译器被骗了,好忧伤……),甭管后面有没有前提,都是要再来执行一次配方的。
那有人问啦,如果有前提,但是符合我说的是伪目标咋办,有前提就判断啊,自然接着向上找把前提当目标的那个规则再继续判断啊,然后倒着回来层层执行配方就行了。
目标和伪目标就一点儿区别,就是一个生成文件,一个生不成文件,中间的前提判断之类的都是一样的,这和伪不伪没有关系!
最后,就是那个.PHONY的声明,它其实就一个作用,把一个东西明明白白的告诉编译器,这个是个伪目标,没有实体文件。前面说,不这么做也是,为啥还要专门声明,这就是大家应该都看过的例子了,就是当文件夹里有个叫clean的,那这样的话,你如果不声明,要执行clean的时候编译器会发现你的那个也叫clean的文件,错把他当成你这个目标文件,然后你又没有前提,自然你这个永远在编译器眼里都是up-to-date,所以配方呢,自然也就不执行了。这个时候为了消除这个误会,才需要加这个显式的生命,告诉编译器,别找啦,我这个clean没有目标文件,你就当没有,然后乖乖给我往下走就完了。
那为啥好多MAKEFILE 写的上来就是个.PHONY: all ........ 其实吧,我觉得这就是个习惯问题,或者说为了保证能执行(菜鸟一只,如果不对,希望大神相告),因为只要你文件夹没有all这个文件,就不会有任何问题,但谁知道呢,程序移植来移植去的,保不齐啊,所以这也算是鲁棒性?不知道是不是可以这么措辞。
下面截了一段陈皓的教程,有严重的错误,必须纠正。全部写在括号里了。
一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标(
不用声明,也是伪目标
),其依赖于其它三个目标。由于伪目标的特性是,总是被执行的(这倒是对的,因为伪目标没有嘛,所以总"意图“要创建),所以其依赖的那三个目标就总是不如“all”这个目标新(
这是绝对不对的,应该是:all都没有了,不管后面的前提到底新不新,都要执行的,虽然也没有配方可以执行,但步骤要继续啊,前提要一个个接着查下去,查它们是不是存在,是不是最新
)。所以,其它三个目标的规则总是会被决议(这个好拗口,就是检查那三个东西在不在,新不新,不在或者不新就接着运行他们三个自己的规则)。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”(
这里的声明只是为了要避免文件夹里同名all文件的存在)。
最终感想:还是得看官方的啊,虽然是英文的,我要加油学英语!