1. 编译 C 程序
“ N.o ”自动由“ N.c ” 生成,执行命令为“ $(CC) -c $(CPPFLAGS) $(CFLAGS) ”。
2. 编译 C++ 程序
“ N.o ”自动由“ N.cc ”或者“ N.C ” 生成,执行命令为“ $(CXX) -c $(CPPFLAGS) $(CFLAGS) ”。建议使用“ .cc ”作为 C++ 源文件的后缀,而不是“ .C ”
3. 汇编和需要预处理的汇编程序
“ N.s ”是不需要预处理的汇编源文件,“ N.S ”是需要预处理的汇编源文件。汇编器为“ as ”。
“ N.o ” 可自动由“ N.s ”生成,执行命令是:“ $(AS) $(ASFLAGS) ”。
“ N.s ” 可由“ N.S ”生成, C 预编译器“ cpp ”,执行命令是:“ $(CPP) $(CPPFLAGS) ”。
4. 链接单一的 object 文件
“ N ”自动由“ N.o ”生成,通过 C 编译器使用链接器( GUN ld ),执行命令是:“ $(CC) $(LDFLAGS) N.o $(LOADLIBES) $(LDLIBS) ”。
此规则仅适用:由一个源文件直接产生可执行文件的情况。当需要有多个源文件共同来创建一个可执行文件时,需要在 Makefile 中增加隐含规则的依赖文件。例如:
x : y.o z.o
当“ x.c ”、“ y.c ”和“ z.c ”都存在时,规则执行如下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o
在复杂的场合,目标文件和源文件之间不存在向上边那样的名字对应关系时(“ x ”和“ x.c ”对应,因此隐含规则在进行链接时,自动将“ x.c ”作为其依赖文件)。这时,需要在 Makefile 中明确给出描述目标依赖关系的规则。
通常, gcc 在编译源文件时(根据源文件的后缀名来启动相应的编译器),如果没有指定“ -c ”选项, gcc 会在编译完成之后调用“ ld ”连接成为可执行文件。
AR
函数库打包程序,可创建静态库 .a 文档。默认是“ ar ”。
AS
汇编程序。默认是“ as ”。
CC
C 编译程序。默认是“ cc ”。
CXX
C++ 编译程序。默认是“ g++ ”。
CPP
C 程序的预处理器(输出是标准输出设备)。默认是“ $(CC) -E ”。
RM
删除命令。默认是“ rm -f ”。
ARFLAGS
执行“ AR ”命令的命令行参数。默认值是“ rv ”。
ASFLAGS
执行汇编语器“ AS ”的命令行参数(明确指定“ .s ”或“ .S ”文件时)。
CFLAGS
执行“ CC ”编译器的命令行参数(编译 .c 源文件的选项)。
CXXFLAGS
执行“ g++ ”编译器的命令行参数(编译 .cc 源文件的选项)。
CPPFLAGS
执行 C 预处理器“ cc -E ”的命令行参数( C 和 Fortran 编译器会用到)。
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
$@
表示规则的目标文件名。如果目标是一个文档文件( Linux 中,一般称 .a 文件为文档文件,也称为静态库文件),那么它代表这个文档的文件名。在多目标模式规则中,它代表的是哪个触发规则被执行的目标文件名。
$%
当规则的目标文件是一个静态库文件时,代表静态库的一个成员名。例如,规则的目标是“ foo.a(bar.o) ”,那么,“ $% ”的值就为“ bar.o ”,“ $@ ”的值为“ foo.a ”。如果目标不是静态库文件,其值为空。
$<
规则的第一个依赖文件名。如果是一个目标文件使用隐含规则来重建,则它代表由隐含规则加入的第一个依赖文件。
$?
所有比目标文件更新的依赖文件列表,空格分割。如果目标是静态库文件名,代表的是库成员( .o 文件)。
$^
规则的所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有库成员( .o 文件)名。一个文件可重复的出现在目标的依赖中,变量“ $^ ”只记录它的一次引用情况。就是说变量“ $^ ”会去掉重复的依赖文件。
$+
类似“ $^ ”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。
$*
在模式规则和 静态模式规则 中,代表“茎”。“茎”是目标模式中“ % ”所代表的部分(当文件名中存在目录时,“茎”也包含目录(斜杠之前)部分)。例如:文件“ dir/a.foo.b ”,当目标的模式为“ a.%.b ”时,“ $* ”的值为“ dir/a.foo ”。“茎”对于构造相关文件名非常有用。
GUN make 中,还可以通过这七个自动化变量来获取一个完整文件名中的目录部分和具体文件名部分。在这些变量中加入“ D ”或者“ F ”字符就形成了一系列变种的自动环变量。
$(@D)
表示目标文件的目录部分(不包括斜杠)。如果“ $@ ”是“ dir/foo.o ”,那么“ $(@D) ”的值为“ dir ”。如果“ $@ ”不存在斜杠,其值就是“ . ”(当前目录)。注意它和 函数“ dir” 的区别!
$(@F)
目标文件的完整文件名中除目录以外的部分(实际文件名)。如果“ $@ ”为“ dir/foo.o ”,那么“ $(@F) ”只就是“ foo.o ”。“ $(@F) ”等价于函数“ $(notdir $@) ”。
$(*D)
$(*F)
分别代表目标“茎”中的目录部分和文件名部分。
$(%D)
$(%F)
当以如“ archive(member) ”形式静态库为目标时,分别表示库文件成员“ member ”名中的目录部分和文件名部分。它仅对这种形式的规则目标有效。
$(<D)
$(<F)
分别表示规则中第一个依赖文件的目录部分和文件名部分。
$(^D)
$(^F)
分别表示所有依赖文件的目录部分和文件部分(不存在同一文件)。
$(+D)
$(+F)
分别表示所有依赖文件的目录部分和文件部分(可存在重复文件)。
$(?D)
$(?F)
分别表示被更新的依赖文件的目录部分和文件名部分。
假如在一个存在万用规则的 Makefile 中提及了文件“ foo.c ”。为了创建这个目标, make 会试图使用以下规则来创建这个目标: 1. 对一个 .o 文件“ foo.c.o ”进行链接并产生文件“ foo.c ”; 2. 使用 c 编译和连接程器由文件“ foo.c.c ”来创建这个文件; 3. 编译并链接 Pascal 程序“ foo.c.p ”来创建;等等。总之 make 会试图使用所有可能的隐含规则来完成对这个文件的创建。
当然,我们很清楚以上这样的过程是没有必要的,我们知道“ foo.c ”是一个 .c 原文件,而不是一个可执行程序。 make 在执行时都会试图根据可能的隐含规则来创建这个文件,但由于其依赖的文件(“ foo.c.o ”、“ foo.c.c ”等)不存在,最终这些可能的隐含规则都会被否定。但是如果在 Makefile 中存在一个万用规则,那么 make 执行时所要考虑的情况就比较复杂,也很多(它会试图通过隐含规则来创建那些依赖文件,虽然最终这些文件不可能被创建,也无从创建),从而导致 make 的执行效率会很低。 通常有两种方式,需要在定义一个万用规则时对其进行限制。
1. 将万用规则设置为最终规则,定义时使用双冒号规则。作为最终规则,此规则只有在它的依赖文件存在时才能被应用,即使它的依赖可以由隐含规则创建也不行。就是说,最终规则中没有进一步的“链”。
例如,从 RCS 和 SCCS 文件中提取源文件的内嵌隐含规则就是一个最终规则。因此如果文件“ foo.c,v ”不存在, make 就不会试图从一个中间文件“ foo.c,v.o ”或“ RCS/SCCS/s.foo.c,v ”来创建它。 RCS 和 SCCS 文件一般都是最终的源文件,它不能由其它任何文件重建; make 可以记录它的时间戳,但不会寻找重建它们的方式。
如果万用规则没有定义为最终规则,那么它就是一个非最终规则。非最终的万用规则不会被用来创建那些符合某一个明确模式规则的目标和依赖文件。就是说如果在 Makefile 中存在匹配此文件的模式规则(非万用规则),那么对于这个文件来说其重建规则只会是它所匹配的这个模式,而不是这个非最终的万用规则。例如,文件“ foo.c ”,如果在 Makefile 中同时存在一个万用规则和模式规则 “ %.c : %.y ”(该规则运行 Yacc )。无论该规则是否会被使用(如果存在文件“ foo.y ”,那么规则将被执行)。那么 make 试图重建“ foo.c ”的规则都是“ %.c : %.y ”,而不是万用规则。这样做是为了避免 make 执行时试图使用非最终的万用规则来重建文件“ foo.c ”的情况发生。
2. 定义一个特殊的内嵌哑模式规则给出如何重建某一类文件,避免使用非最终万用规则。哑模式规则没有依赖,也没有命令行,在 make 的其它场合被忽略。例如,内嵌的哑模式规则:“ %.p : ”为 Pascal 源程序如“ foo.p ”指定了重建规则(规则不存在依赖文件、也不存在任何命令),这样就避免了 make 试图“ foo.p ”而去寻找“ foo.p.o ”或“ foo.p.c ”的过程。
我们可以为所有的 make 可识别的后缀创建一个形如“ %.p : ”的哑模式规则。
%.o : %.c
$(CC) $(CFLAGS) –D__DEBUG__ $< -o $@
有时 make 会需要一个缺省的规则,在执行过程中无法为一个文件找到合适的重建规则 ,那么 make 就使用这个规则来重建它。
%::
touch $@
或
.DEFAULT :
touch $@
例如:“ .c ”和“ .o ”都是 make 可识别的后缀。因此当定义了一个目标是“ .c.o ”的规则时。 make 会将它作为一个双后缀规则来处理,它的含义是所有“ .o ”文件的依赖文件是对应的“ .c ”文件。下边是使用后 缀 规则定义的编译 .c 源文件的规则:
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
注意:一个后缀规则中不存在任何依赖文件。否则,此规则将被作为一个普通规则对待。因此规则:
.c.o: foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
就不是一个后缀规则。它是一个目标文件为“ .c.o ”、依赖文件是“ foo.h ”的普通规则。
可识别的后缀指的是特殊目标“ .SUFFIXES ”所有依赖的名字。通过给特殊目标“ SUFFIXES ”添加依赖来增加一个可被识别的后缀。像下边这样:
.SUFFIXES: .hack .win
ARCHIVE(MEMBER)
foolib(hack.o) : hack.o
ar cr foolib hack.o
foolib(hack.o kludge.o)
foolib(*.o )
使用“ nm ”命令查看静态库的成员
静态库文件需要使用“ ar ”来创建和维护。当给静态库增建一个成员时(加入一个 .o 文件到静态库中),“ ar ”可直接将需要增加的 .o 文件简单的追加到静态库的末尾。之后当我们使用这个库进行连接生成可执行文件时,链接程序“ ld ”却提示错误,这可能是:主程序使用了之前加入到库中的 .o 文件中定义的一个函数或者全局变量,但连接程序无法找到这个函数或者变量。
这个问题的原因是:之前我们将编译完成的 .o 文件直接加入到了库的末尾,却并没有更新库的有效符号表。连接程序进行连接时,在静态库的符号索引表中无法定位刚才加入的 .o 文件中定义的函数或者变量。这就需要在完成库成员追加以后让加入的所有 .o 文件中定义的函数(变量)有效,完成这个工作需要使用另外一个工具“ ranlib ”来对静态库的符号索引表进行更新。
我们所使用到的静态库(文档文件)中,存在这样一个特殊的成员,它的名字是“ __.SYMDEF ”。它包含了静态库中所有成员所定义的有效符号(函数名、变量名)。因此,当为库增加了一个成员时,相应的就需要更新成员“ __.SYMDEF ”,否则所增加的成员中定义的所有的符号将无法被连接程序定位。完成更新的命令是:
ranlib ARCHIVEFILE
通常在 Makefile 中我们可以这样来实现:
libfoo.a: libfoo.a(x.o) libfoo.a(y.o) ...
ranlib libfoo.a
它所实现的是在更新静态库成员“ x.o ”和“ y.o ”之后,对静态库的成员“ __.SYMDEF ”进行更新(更新库的符号索引表)。
如果我们使用 GNU ar 工具来维护、管理静态库,我们就不需要考虑这一步。 GNU ar 本身已经提供了在更新库的同时更新符号索引表的功能 。
.c.a:
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o