为什么gnumake会重新生成中间文件



我有一个非常简单的makefile,如下所示:

.PHONY: clean all
CC              = /home/utils/gcc-5.2.0/bin/g++
CFLAGS          = -Wall -Werror -fPIC 
SRC             = $(wildcard *.c)
OBJ             = $(subst .c,.o,$(SRC))
.INTERMEDIATE: $(OBJ)
all: test.so
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
test.so: $(OBJ)
$(CC) -shared $^ -o $@
clean:
@rm -f *.o *~ *.so

我在同一目录中只有两个文件:a.c和b.c当我执行"make all"时,我得到了以下内容,这是完美的。

/home/utils/gcc-5.2.0/bin/g++ -Wall -Werror -fPIC  -o a.o -c a.c
/home/utils/gcc-5.2.0/bin/g++ -Wall -Werror -fPIC  -o b.o -c b.c
/home/utils/gcc-5.2.0/bin/g++ -shared a.o b.o -o test.so
rm a.o b.o

但是,如果我这样做:触摸交流;使所有

我得到了与上面相同的make执行序列,这不是我所期望的。a.c和b.c之间没有依赖关系。我期望的是:

/home/utils/gcc-5.2.0/bin/g++ -Wall -Werror -fPIC  -o a.o -c a.c
/home/utils/gcc-5.2.0/bin/g++ -shared a.o b.o -o test.so
rm a.o

我不明白为什么要重新编译b.c。根据gnumake手册:

第一个区别是如果中间文件不存在会发生什么。如果一个普通文件b不存在,并且make考虑了一个依赖于b的目标,那么它总是创建b,然后从b更新目标。但如果b是一个中间文件,那么make可以很好地保留它。它不会麻烦更新b或最终目标,除非b的某些先决条件比该目标更新,或者有其他原因更新该目标。

b.o是一个中间文件,不应该再次编译,因为b.c没有更改。我错过了什么?

如果未重新编译b.o,则无法创建test.so,因为它依赖于b.o。如果删除了b.o,则无法从a.ob.o创建共享库。

如果您试图创建一个静态库,那么您可以使用make的特殊归档语法来替换a.o,而无需重新编译b.o,但除非您使用归档特殊语法,否则这是不可能的,而且对于共享库(如您试图在此处构建的库),这是不可行的。

默认情况下,这些.o文件不被视为中间文件是有原因的,通过添加.INTERMEDIATE目标来强制它们并不意味着可以避免重建它们。

您的困难源于您所说的中间文件并不是GNUMake的意思;但.INTERMEDIATE的特殊目标允许你坚持a.ob.o,它们在您的makefile中不是GNU Make的中间文件意义,应被视为它们是GNU Make's sense中的中间,并且那么后果会让你大吃一惊。

所说的F是一个中间文件的意思是:F是目标T的先决条件,并且F本身具有先决条件。因此CCD_ 14和CCD_test.so,因为它们是该目标的先决条件,并且它们本身具有各自的先决条件a.c和b.c`.

中间的意义上,使目标test.so不需要(a.o|b.o),除非它早于(a.c|b.c)。所以你被这个事实吓了一跳当a.ob.o被设置为.INTERMEDIATE时,GNU Make在设置test.so:时总是删除它们

b.o是一个中间文件,不应该再次编译,因为b.c没有更改。我错过了什么?

您主要错过了10.4条隐式规则链

通常,如果在生成文件中提到文件是目标或先决条件,则该文件不能是中间文件。但是,您可以通过将文件列为特殊目标.intermediate的先决条件,将其明确标记为中间文件。即使以其他方式明确提及该文件,这也会生效。

因此,a.ob.o在GNU Make的意义上通常不会是中间的,因为它们在您的makefile中被提及为test.so的前提条件。但你可以在GNUMake的意义上,通过将它们列为.INTERMEDIATE的先决条件,使它们被视为中间体——就像您所做的那样。

GNU Make所说的中间比你的意思更具技术性在手册的同一节中进行了解释:

有时一个文件可以由一系列隐式规则组成。例如,文件n.o可以由n.y通过先运行Yacc然后运行cc来生成。这样的序列被称为链。

如果文件n.c存在,或者在makefile中提到,则不需要进行特殊搜索:make发现对象文件可以从n.C通过C编译生成;稍后,当考虑到如何生成n.c,使用了运行Yacc的规则。最终,n.c和n.o都会更新。

然而,即使n.c不存在,也没有被提及,make也知道如何将其设想为n.o和n.y之间缺少链接在这种情况下,n.c被称为中间文件。一旦make决定使用中间文件,它就会作为如果在makefile中提到了它,以及说明如何创建它的隐含规则

(我的重点)。更简单地说,如果Make需要使目标输出,并发现文件输入即:

  • 它没有明确的规则使输出来自输入,但是-
  • 它通过以下方式知道一系列隐式(也称为内置)规则不是makefile中的目标或先决条件的文件阶段可以从输入中生成,并从中生成输出,然后它将采用使输出的规则序列从通过阶段输入,并且将是中间文件

至关重要的是,由于阶段不是您的目标之一,也不是其中任何一个的先决条件,因此知道它仅仅是从输入产生输出一次性副产品,并且因此可以在其已经达到该目的时被删除。

这里有一个简单的示例项目,涉及GNUMake意义上真正中间的文件。我们在项目目录中有一些yaccyacc.ylexlex.l,我们想用它们构建一个解析器parse,并使用以下makefile:

生成文件

YFLAGS := -d
OBJS := yacc.o lex.o
.PHONY: all clean
all: parse
parse: $(OBJS)
$(CC) $^ -o $@
clean:
$(RM) parse $(OBJS)

它的运行方式如下:

$ make
yacc -d yacc.y
mv -f y.tab.c yacc.c
cc    -c -o yacc.o yacc.c
lex  -t lex.l > lex.c
cc    -c -o lex.o lex.c
cc yacc.o lex.o -o parse
rm lex.c yacc.c

现在你可以看到,Make使用其隐含规则目录可以通过编译不存在的C源文件来生成yacc.oyacc.c,并且yacc.c可以从现有的源文件生成通过运行CCD_ 39来执行CCD_。同样,它发现lex.o可以通过编译不存在的C源lex.clex.c可以通过运行CCD_ 44由CCD_。则parse为由yacc.olex.o通过makefile指定。

但是您的makefile中没有任何关于yacc.clex.c的内容。远至你已经告诉Make,这样的文件是否存在对你来说并不重要。它们是中间文件,只是.y -> .o.l -> .o产品中的阶段。所以当Make完成时:

rm lex.c yacc.c

如果你:

$ touch yacc.y

再次:

$ make
yacc -d yacc.y
mv -f y.tab.c yacc.c
cc    -c -o yacc.o yacc.c
cc yacc.o lex.o -o parse
rm yacc.c

对于parse的链接,只有yacc.o被重新制作,并且删除了这次生成的一个中间文件yacc.c

底线

你的对象文件不是GNU意义上的中间文件,你也没有希望他们被当作自己一样对待。这对于对象文件来说是正常的。因此,删除.INTERMEDIATE: $(OBJ)

最新更新