Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

2. 隐含规则和模式规则

上一节的 Makefile 写得中规中矩,比较繁琐,是为了讲清楚基本概念,其实 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
	gcc main.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:
	-rm main *.o

.PHONY: clean

这不是比原来更繁琐了吗?现在可以把提出来的三条规则删去,写成:

main: main.o stack.o maze.o
	gcc main.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:
	-rm main *.o

.PHONY: clean

这就比原来简单多了。可是现在 main.ostack.omaze.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 编译器。

$ which cc
/usr/bin/cc
$ ls -l /usr/bin/cc
lrwxrwxrwx 1 root root 20 2008-07-04 05:59 /usr/bin/cc -> /etc/alternatives/cc
$ ls -l /etc/alternatives/cc
lrwxrwxrwx 1 root root 12 2008-11-01 09:10 /etc/alternatives/cc -> /usr/bin/gcc

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
	gcc main.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:
	-rm main *.o

.PHONY: clean

我们知道,写规则的目的是让 make 建立依赖关系图,不管怎么写,只要把所有的依赖关系都描述清楚了就行。对于多目标的规则, make 会拆成几条单目标的规则来处理,例如

target1 target2: prerequisite1 prerequisite2
	command $< -o $@

这样一条规则相当于:

target1: prerequisite1 prerequisite2
	command prerequisite1 -o target1

target2: prerequisite1 prerequisite2
	command prerequisite1 -o target2

注意两条规则的命令列表是一样的,但 $@ 的取值不同。