红联Linux门户
Linux帮助

linux编程初步之跟我学makefile中的自动依赖

发布时间:2009-04-13 17:10:49来源:红联作者:kevin_2009
最近学习了一下makefile,对自动依赖感觉不是很懂,通过阅读info make和网上的一些文章,然后做了一下测试,感觉有了一些认识,所以把学习的一些过程写出来,希望对像我一样的初学者有所帮助。其中可能有很多错误,还请各位大侠指正。
yeahnix,04/5/26

1.依赖及自动依赖
makefile的执行是以依赖关系为基础的,即如果要生成某个目标,那么该目标所依赖的文件必须存在,或者根据一些规则可以构造出来,然后再根据构造该目标的规则生成目标。makefile中的规则可以分为:显式规则、静态规则、模式规则和隐含规则(具体含义参见info make)。
在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是:
main.o : main.c defs.h
但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用C/C++编译器的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:
cc -M main.c
其输出是:
main.o : main.c defs.h
于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。
那么如何让编译器生成的这些依赖关系自动包含在makefile中呢?通过查阅网上的一些文章和info make,我看到了三种方法,在方便理解的情况下姑且称之为info make的sed方法,depend方法和编译器指令-MD方法。

2.info make中的自动依赖及遇到的问题
info make中提到了sed方法:
为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。
于是,我们可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新或自成[.d]文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。
下面这个模式规则产生[.d]文件:
%.d: %.c
set -e; $(CC) -M $(CPPFLAGS) $< \
| sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@; \
[ -s $@ ] || rm -f $@
这个规则的意思是,所有的[.d]文件依赖于[.c]文件,set -e 指定shell遇到错误即退出。sed命令做了一个替换,把依赖关系:
main.o : main.c defs.h //1
转成: //1
main.o main.d : main.c defs.h //1
于是,[.d]文件也会自动更新了,并会自动生成了。[ -s $@ ] || rm -f $@的意思是删除产生的空.d文件,从而减少不必要的处理过程。
然后使用Makefile的“include”命令,
sources = foo.c bar.c
include $(sources:.c=.d)
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d]。这样当include发现没有.d文件时候就会自动生成.d文件,同时根据.d文件的生成规则,也就生成了.o文件对应.c和.h文件的依赖关系。

开始时对info make中描述的//1部分的作用没有能很好的理解,.d文件的自动更新也理解不深,于是决定测试一下。编写了一个简单的程序,如下:
[root@yeahnix test_makefile]#pwd
/root/Projects/test_makefile
[root@yeahnix test_makefile]# ls -R
.:
aa.c bb.c bb.h dir Makefile
./dir:
echomsg.c echomsg.h
其中aa.c调用bb.c,bb.c调用echomsg.c。
最开始时,echomsg.c和echomsg.h并没有在dir目录下,于是利用上述规则构造makefile,发现很正常,程序够造了出来,.d文件也出来了(好像info make中说.d文件应该是作为中间文件,可以自动删除的,先不管他,反正程序出来了),但是make开始会报错,aa.d没有找到......,后来重新读info make,发现可以用-include替代,因为刚开始没有找到是正常的,替换后,没有找到就不会报错了。
于是想看看是不是包含子目录也正确呢?将echomsg.c和echomsg.h放在dir目录下,发现sed命令执行不正确,怎么会执行不正确呢?难道info make也会讲错,想不通。是不是-include有问题。于是将include前加了一个TAB符号,一切正常,程序出来了,连.d文件也没有了,以为真的自动删除了。心里很高兴,原来-include应该这么用就对了嘛。但是当用make -d察看调试信息时,发现根本就没有生成.d文件的过程,这说明这个模式规则根本就没有起作用。
后来终于发现,include命令前根本就不该加TAB,否则include指令被认为是shell命令,根本就没有起作用!!!看来还是应该多读手册啊。重新让include起作用,当然还是sed命令出错,然后才想起由于子目录中包括“/”,而sed的s命令以/为分隔符,结果当然是sed命令搞不清楚该怎么办了。想到vim中可以以+替换/作为分隔符,于是马上试,在shell中执行:
echo dir/echomsg.o : dir/echomsg.c | sed 's+\($*)\.o[ :]*+\1.o \1.d : dir/echomsg.c+g'
结果显示正确,于是放到makefile中,make,发现.d文件生成了,但是其中并不包含main.o main.d : main.c这样的内容,而只是包含main.o : main.c这样的内容,好像sed命令根本没有执行过,而只是由编译器产生依赖关系后直接放在.d文件中的,但是实际上sed命令又是执行过的(通过make -d察看)。这个问题实在想不通,到现在也想不通。于是到处查资料,决定试试另外两种方法。

3.depend文件和-MD方法
所谓depend方法即用$(CC) -MM $(CPPFLAGS) $^ > depend将依赖关系放到depend文件中,然后再include depend就行了。发现这个方法确实行,通过make -d察看调试信息,发现当更新了.c文件时,depend文件自动重新生成,但是当更新.h时,depend并不自动重新生成.不知道这个会不会影响正确性,同时,在make之前,还得make depend,好像不是很好。而且,在自动重新生成depend时,必须扫描全部的源文件。info make中说这种方法过时了.
-MD方法则是利用编译器参数-MD代替-M,从而由编译器自动生成.d文件,可以用下面的模式规则来定义。
%.d:%.c
$(CC) $(CFLAGS) -MD -E $<
其中-E指示编译器仅仅处理预处理阶段,而不继续进行编译和链接,否则,会报链接错误。这种方法生成的.d文件中也不包括main.o main.d : main.c这样的内容,而仅仅包含main.o : main.c这样的内容。所以,当更新了.c文件时,.d文件自动重新生成,但是当更新.h时,.d并不自动重新生成。不过,由于每个.c文件都对应了一个.d文件,所以,仅仅扫描更改过的源文件,从而生成对应的.d文件。
4.修改sed方法
在用前面的sed方法不能达到目的时,考虑到sed命令的目的是生成main.o main.d : main.c这样的内容,所以考虑是否有另外的方法达到目的。由于$*中有目录部分时就会有/,所以考虑是不是可以只要文件名部分。实际上应该是可以的,因为编译器生成的依赖关系中,.o文件本身就只有文件名部分,而至于以后的编译过程,则编译器可以通过-I来指定的搜索路径找到对应的文件。在重新阅读info make后,发现可以用$(*F)来代替$*,从而解决了问题。以下是一个测试上述方法的makefile文件,写的并不好,但是能够达到测试的目的 :-)

default_target : all
#dir:=/root/Projects/test_makefile /root/Projects/test_makefile/dir
dir:= . ./dir
INCLUDE:= $(foreach dir_internal, $(dir), $(addprefix -I, $(dir_internal)))
CC=gcc
src:=$(foreach dir_internal, $(dir), $(wildcard $(dir_internal)/*.c))
CFLAGS += $(INCLUDE)
%.o:%.c
$(CC) -c $(CFLAGS) $< -o $*.o
obj=$(src:.c=.o)
%.d:%.c
set -e; $(CC) -MM $(CFLAGS) $(CCFLAGS) $< \
| sed 's,\($(*F)\)\.o[ :]*,\1.o $*.d : ,g' > $*.d; \
[ -s $@ ] || rm -f $@
-include $(src:.c=.d)
all : $(obj)
$(CC) -o exe $(CFLAGS) $^

那么.d文件又是如何被重新生成的呢?在通过make -d察看调试信息时,大致明白了makefile的处理过程,从而也对所谓的makefile总是被自动重新生成有了一些理解。对于最开始执行的makefile和通过$(MAKE) -C方法执行的makefile总是会在处理完依赖关系后(生成了依赖关系图)被重新执行一遍(这就是make的两阶段过程)第二次执行中,执行target部分,而对于其中include的makefile文件,如果对应的文件存在,它先读入其内容,然后还会搜寻是否有规则可以更新这个文件,如果有,且其依赖新于这个makefile,就会重新生成,然后再一次include。因此,当aa.d文件被include时,由于其内容是
aa.o aa.d : aa.c \
/root/Projects/test_makefile/bb.h \ ....
这样的形式,所以当对应.o文件所依赖文件中的一个任何一个被更新后,.d文件被更新,从而被重新include,因此,依赖关系也被重建。

5.总结
搞了好多天,总算对info make中自动依赖的实现所给出的模式规则有了一定的了解,但是还是有一些问题不是很明白。
a:其中好像说depend方法对应生成的.o文件由于不是intermediate文件,所以不会被自动删除,而sed方法则使其过时,其潜台词是否是指.d文件会被自动删除,我测试结果并没有删除,而且好像也不应该删除,要不又要完全重新生成,不是跟make clean后的效果一样了吗,不解!
b:在没有使用$(*F)替换$*前,sed的s命令中用=和,作为分隔符,在shell中测试都可以,为什么在makefile中就不能达到同样的效果呢?
c:关于.d文件对于.h文件的依赖性,当任何.h文件改变时,依赖于它的.d文件都被更新,这个更新的作用是什么,有这个必要吗?

还请各位大侠指点。不胜感激!
文章评论

共有 8 条评论

  1. bybandzk 于 2014-04-18 17:00:34发表:

    看看

  2. 西岸阳光 于 2014-03-27 16:56:23发表:

    谢谢分享

  3. 曾仕军 于 2014-03-19 10:34:23发表:

    很好的东西

  4. nafnafab 于 2009-07-24 15:42:12发表:

    好啊.学习学习.

  5. balini 于 2009-07-22 13:58:55发表:

    学习学习

  6. 微宝贝 于 2009-05-13 00:06:21发表:

    学习中

  7. abea007 于 2009-05-10 21:03:53发表:

    好啊.学习.

  8. angline 于 2009-04-13 18:01:11发表:

    我要向楼主学习