C語言的本質(37)——makefile之隱含規則和模式規則

尹成發表於2014-07-24

Makefile有很多靈活的寫法,可以寫得更簡潔,同時減少出錯的可能。本節我們來看看這樣一個例子還有哪些改進的餘地。

 一個目標依賴的所有條件不一定非得寫在一條規則中,也可以拆開寫,例如:

main.o: main.h stack.h maze.h
 
main.o: main.c
         gcc-c main.c

就相當於:

main.o: main.c main.h stack.h maze.h
         gcc-c main.c


如果一個目標拆開寫多條規則,其中只有一條規則允許有命令列表,其它規則應該沒有命令列表,否則make會報警告並且採用最後一條規則的命令列表。

 

這樣我們的例子可以改寫成:

main: main.o stack.o maze.o
         gccmain.o stack.o maze.o -o main
 
main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h
 
main.o: main.c
         gcc-c main.c
 
stack.o: stack.c
         gcc-c stack.c
 
maze.o: maze.c
         gcc-c maze.c
 
clean:
         -rmmain *.o
 
.PHONY: clean

這不是比原來更繁瑣了嗎?現在可以把提出來的三條規則刪去,寫成:

main: main.o stack.o maze.o
         gccmain.o stack.o maze.o -o main
 
main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h
 
clean:
         -rmmain *.o
 
.PHONY: clean

這就比原來簡單多了。可是現在main.o、stack.o和maze.o這三個目標連編譯命令都沒有了,怎麼編譯的呢?試試看:

 

$ make
cc   -c -o main.o main.c
cc   -c -o stack.o stack.c
cc   -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main

現在解釋一下前三條編譯命令是怎麼來。如果一個目標在Makefile中的所有規則都沒有命令列表,make會嘗試在內建的隱含規則(Implicit Rule)資料庫中查詢適用的規則。make的隱含規則資料庫可以用make -p命令列印,列印出來的格式也是Makefile的格式,包括很多變數和規則,其中和我們這個例子有關的隱含規則有:

 

# default
OUTPUT_OPTION = -o $@
 
# default
CC = cc
 
# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS)$(TARGET_ARCH) -c
 
%.o: %.c
# commands to execute (built-in):
       $(COMPILE.c) $(OUTPUT_OPTION) $<


#號在Makefile中表示單行註釋,就像C語言的//註釋一樣。CC是一個Makefile變數,用CC = cc定義和賦值,用$(CC)取它的值,其值應該是cc。Makefile變數像C的巨集定義一樣,代表一串字元,在取值的地方展開。cc是一個符號連結,通常指向gcc,在有些UNIX系統上可能指向另外一種C編譯器。


 

CFLAGS這個變數沒有定義,$(CFLAGS)展開是空,CPPFLAGS和TARGET_ARCH也是如此。這樣$(COMPILE.c)展開應該是cc 空 空 空 -c,去掉“空”得到cc -c,注意中間留下4個空格,所以%.o:%.c規則的命令$(COMPILE.c) $(OUTPUT_OPTION) $<展開之後是cc -c -o $@$<,和上面的編譯命令已經很接近了。

 

$@和$<是兩個特殊的變數,$@的取值為規則中的目標,$<的取值為規則中的第一個條件。%.o: %.c是一種特殊的規則,稱為模式規則(Pattern Rule)。現在回顧一下整個過程,在我們的Makefile中以main.o為目標的規則都沒有命令列表,所以make會查詢隱含規則,發現隱含規則中有這樣一條模式規則適用,main.o符合%.o的模式,現在%就代表main(稱為main.o這個名字的Stem),再替換到%.c中就是main.c。所以這條模式規則相當於:

 

main.o: main.c
         cc    -c -o main.o main.c

隨後,在處理stack.o目標時又用到這條模式規則,這時又相當於:

stack.o: stack.c
         cc    -c -o stack.o stack.c


maze.o也同樣處理。這三條規則可以由make的隱含規則推匯出來,所以不必寫在Makefile中。

 

先前我們寫Makefile都是以目標為中心,一個目標依賴於若干條件,現在換個角度,以條件為中心,Makefile還可以這麼寫:

main: main.o stack.o maze.o
         gccmain.o stack.o maze.o -o main
 
main.o stack.o maze.o: main.h
main.o maze.o: maze.h
main.o stack.o: stack.h
 
clean:
         -rmmain *.o
 
.PHONY: clean

我們知道,寫規則的目的是讓make建立依賴關係圖,不管怎麼寫,只要把所有的依賴關係都描述清楚了就行。對於多目標的規則,make會拆成幾條單目標的規則來處理,例如

target1 target2: prerequisite1prerequisite2
         command$< -o $@

這樣一條規則相當於:

target1: prerequisite1 prerequisite2
         commandprerequisite1 -o target1
 
target2: prerequisite1 prerequisite2
         commandprerequisite1 -o target2

注意兩條規則的命令列表是一樣的,但$@的取值不同。

 

 

相關文章