你的位置:首页 > 操作系统

[操作系统]makefile要点


  在阅读大型工程C源码时不可避免的需要了解makefile文件,它定义了源文件的编译规则和链接规则,是阅读与编写源码都必须了解的知识,本文通过学习陈皓写的一份makefile中文教程,将其要点梳理如下,以备自己回顾之用。原始教程请参考陈皓博客或直接下载网友做好的PDF教程,直接百度即可。

 

一、概念

  一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。

  make 是一个命令工具,是一个解释 makefile 中指令的命令工具。

 

二、语法

2.1格式与组成

  格式如下:

target ... : prerequisites ...command

 

  说明:这是一个文件的依赖关系,也就是说,target 这一个或多个的目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command 中。说白一点就是说, prerequisites 中如果有一个以上的文件比 target 文件要新的话,command 所定义的命令就会被执行。这就是 Makefile 的规则。命令必须以tab开头。

  组成:

  Makefile 里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。显式规则就是我们定义出来的,隐晦规则即自动推导规则(make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中),支持变量定义类似于宏替换,注释提高可读性,文件指示包括:引入其他makefile文件,指定makefile的有效部分,定义一个多行命令。

 

2.2规则

         2.2.1规则的语法

targets : prerequisitescommand...或是这样:targets : prerequisites ; commandcommand

 

      2.2.2 文件搜索

  (1)当 make 需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉 make,让make 自动去找。特殊变量“VPATH”就是完成这个功能的(如:VPATH = src:../headers,目录用冒号分隔),make在当前目录找不到就到变量VPATH路径下找。

  (2)另一个设置文件搜索路径的方法是使用 make 的“vpath”关键字 (注意,它是全小写的),格式为:vpath <pattern> <directories>,表示在directories下搜索符合pattern模式的文件。其中pattern需要使用%符号,用于表示匹配0或若干个字符。

  2.2.3 多目标与静态模式

Makefile 的规则中的目标可以不止一个,其支持多目标。当然,多个目标的生成规则的执行命令是同一个,不过好在我们的可以使用一个自动化变量“$@”,它表示目前规则中所有目标的集合。示例参见第三节例1.

静态模式可以更加容易地定义多目标的规则,语法如下

<targets ...>: <target-pattern>: <prereq-patterns ...><commands>...

 

target-parrtern 指明了 targets 的模式,也就是的目标集模式。prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。参见第三节例2。

  2.2.4 自动生成依赖性

  在添加删除头文件时,需要修改makefile文件内容,这是繁琐且易错的,所以大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。而为了将编译器自动生成的依赖关系文件与makefile关联起来,GNU 组织建议把编译器为每一个源文件自动生成的依赖关系放到一个文件中,如为“name.c”生成一个“name.d”的 Makefile 文件,[.d]文件中存放着对应[.c]文件的依赖关系,之后将.d文件包含在我们的主 Makefile 中,从而自动化地生成每个文件的依赖性。

产生[.d]文件的模式规则见第三节例3。

  2.2.5命令的关联

  如果希望第二条命令在第一条命令的基础上执行,那么这两条命令应该写在同一行,用分号分隔,而不能写在两行。

  2.2.6 隐含规则

  “隐含规则”是一种惯例,如果我们不明确地写下规则,那么,make 就会在这些内建的规则中寻找所需要的规则和命令。“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数;我们还可以通过“模式规则”的方式写下自己的隐含规则。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。

  (1)隐含规则中所用到的变量

  在隐含规则中的命令,基本上都是使用了一些预先设置的变量。1、你可以在你的 makefile 中改变这些变量的值,2、或是在 make 的命令行中传入这些值,3、或是在你的环境变量中设置这些值,当然,4、你也可以利用 make 的“-R”或“--no–builtin-variables”参数来取消你所定义的变量对隐含规则的作用。

  Make为不同语言建立了不同的编译链接隐含规则,而这些规则都有自身所用到的命令变量和命令参数变量,通过改变这些变量的值可以改变隐含规则的执行方式。

  下面列出常见隐含规则:

语言

推导规则

生成的命令

编译C

“<n>.o”目标的依赖目标会自动推导为“<n>.c”

$(CC) –c $(CPPFLAGS) $(CFLAGS)

编译 C++

“<n>.o”的目标的依赖目标会自动推导为“<n>.cc”或是“<n>.C”

$(CXX) –c (CPPFLAGS) $(CFLAGS)

编译 Pascal

“<n>.o”的目标的依赖目标会自动推导为“<n>.p”

$(PC) –c $(PFLAGS)

编译 Fortran /Ratfor

“<n>.o”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”或“<n>.f”

“.f” “$(FC) –c $(FFLAGS)”

“.F” “$(FC) –c $(FFLAGS) $(CPPFLAGS)”

“.f” “$(FC) –c $(FFLAGS) $(RFLAGS)”

预处理 Fortran/Ratfor

“<n>.f”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”

“.F” “$(FC) –F $(CPPFLAGS) $(FFLAGS)”

“.r” “$(FC) –F $(FFLAGS) $(RFLAGS)”

编译 Modula-2

“<n>.sym”的目标的依赖目标会自动推导为“<n>.def”

“<n.o>” 的目标的依赖目标会自动推导为 “<n>.mod”

$(M2C) $(M2FLAGS) $(DEFFLAGS)

 

$(M2C) $(M2FLAGS) $(MODFLAGS)

汇编和汇编预处理

“<n>.o” 的目标的依赖目标会自动推导为“<n>.s”

默认使用 C 预编译器“cpp”

$(AS) $(ASFLAGS)

 

$(AS) $(ASFLAGS)

链接 Object 文件

“<n>”目标依赖于“<n>.o”,通过运行 C 的编译器来运行链接程序生成( 一 般 是 “ld” )

$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)

……

 

 

         下面列出隐含规则中所用到的变量:包括命令变量和参数变量两种

命令变量

说明

命令参数变量

说明(没指明默认值为空)

AR

函数库打包程序

ARFLAGS

AR 命令的参数。默认值是“rv”。

AS

汇编语言编译程序

ASFLAGS

汇编语言编译器参数。

CC

C 语言编译程序

CFLAGS

C 语言编译器参数。

CXX

C++语言编译程序

CXXFLAGS

C++语言编译器参数。

CO

从 RCS 文件中扩展文件程序

COFLAGS

RCS 命令参数。

CPP

C 程序的预处理器

CPPFLAGS

C /Pascal预处理器参数。

FC

Fortran 和 Ratfor 的编译器和预处理程序

FFLAGS

Fortran 语言编译器参数。

GET

从 SCCS 文件中扩展文件的程序

GFLAGS

SCCS “get”程序参数。

PC

Pascal 语言编译程序

PFLAGS

Pascal 语言编译器参数。

LEX

Lex 方法分析器程序(针对于 C 或 Ratfor)

LFLAGS

Lex 文法分析器参数。

YACC

Yacc 文法分析器(针对于 C 程序)

YFLAGS

Yacc 文法分析器参数。

YACCR

Yacc 文法分析器(针对于 Ratfor 程序)

 

 

TEX

从 TeX 源文件创建 TeX DVI 文件的程序

 

 

RM

删除文件命令

 

 

 

LDFLAGS

链接器参数。(如:“ld”)

 

  (2)模式规则

         可以使用模式规则来定义一个隐含规则。模式规则中,至少在规则的目标定义中要包含"%",否则,就是一般的规则。如果"%"定义在目标中,那么,目标中的"%"的值决定了依赖目标中的"%"的值。示例如下:

%.tab.c %.tab.h: %.ybison -d $<

 

说明:这条规则告诉 make 把所有的[.y]文件都以"bison -d <n>.y"执行,然后生成"<n>.tab.c"和"<n>.tab.h"文件。(其中,"<n>"表示一个任意字符串)。

  (3)后缀规则

  后缀规则是一个比较老式的定义隐含规则的方法。为了和老版本的Makefile 兼容,GNU make 同样兼容于这些东西。后缀规则有两种方式:"双后缀"和"单后缀"。

双后缀规则定义了一对后缀:目标文件的后缀和依赖目标(源文件)的后缀。如".c.o"相当于"%o : %c"。单后缀规则只定义一个后缀,也就是源文件的后缀。如".c"相当于"% : %.c"。

后缀规则不允许任何的依赖文件,如果有依赖文件的话,那就不是后缀规则,那些后缀统统被认为是文件名。

后缀规则中所定义的后缀应该是 make 所认识的,默 认 的 后 缀 列 表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h,.info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。而要让 make知道一些特定的后缀,我们可以使用伪目标".SUFFIXES"来定义或是删除,如:.SUFFIXES: .hack .win把后缀.hack 和.win 加入后缀列表中的末尾;.SUFFIXES: # 删除默认的后缀;SUFFIXES: .c .o .h # 定义自己的后缀。

 

2.3变量

  2.3.1 特殊符号及关键字

  反斜杠(\):是换行符的意思,当一行过长不方便浏览时使用反斜杠换行;另外还有转义的作用,例外是$,如果想要使用$,需要用$$表示。

  横杠(-):加在命令前,表示也许某些文件出现问题,但不要管,继续做后面的事。

  井号(#):注释符,makefile只支持单行注释。可使用注释符结合空变量定义一个空格变量:

nullstring :=space := $(nullstring) # end of the line  其中,nullstring变量什么都没有,space为空格变量。

         通配符*,?,[]:*号表示任意字符,?号任意一个字符,[]表示范围内的字符。

         %:表示匹配0或若干字符,用于pattern模式中。

         objects := $(wildcard *.o):在变量中使用通配符需要使用关键字wildcard指定,否则只会类似于宏替换,不会展开。

         Include(filename):把其它makefile文件包含到当前位置,但命令前不能用tab开头。

         VPATH:makefile中的特殊变量,用于指定make对依赖文件的搜索路径

         vpath:make的关键字,全小写。类似于VPATH,但更灵活

         override:在makefile中设置make命令中指定的变量的值,当make命令中指定了该变量的值时,makefile中该变量值被忽略。

         2.3.2 变量

         (1)自定义变量

         在 makefile 中我们可以使用变量。makefile 的变量也就是一个字符串,类似于C 语言中的宏。不过变量名可以是任意大小写,当定义了变量后,可以使用 “$(变量名)”的方式来使用这个变量了,其实就类似于宏替换。

         变量的命名可以包含字符、数字,下划线(可以是数字开头) ,但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是大小写敏感的, 推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突。

         在变量赋值时,变量的值可以是其他变量的表达式,而其他变量的定义可以在文件的任何位置,即不要求其他变量一定要预先定义。但在递归定义时将会出现无限循环,因此引入操作符“:=”来表示只使用预先定义的变量的值,从而避免变量递归定义时的无限循环。此外,变量的值可以是嵌套的变量表达式,其赋值方式就是按宏替换的方式依次展开求值。

         变量值替换:1“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串;2另外一种变量替换的技术是以“静态模式”(bar := $(foo:%.o=%.c))

         变量作用域:makefile中定义的变量都是全局变量(除了自动化变量),也可定义作用于特定目标或模式的局部变量,格式为:

目标/模式:变量赋值表达式

         (2)自动化变量

         这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。

         $%:仅当目标是函数库文件时,表示规则中的目标成员名。

         $@:表示目前规则中所有的目标的集合

         $<:依赖目标中的第一个目标名字。

         $?:所有比目标新的依赖目标的集合。以空格分隔。

         $^:所有的依赖目标的集合。以空格分隔。

         $+:这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

         $*:这 个 变 量 表 示 目 标 模 式 中 "%"及 其 之 前 的 部 分 。

         这七个自动化变量还可以取得文件的目录名或是在当前目录下的符合模式的文件名, 只需要搭配上"D"或"F"字样。

         (3)命令序列变量

         可以为命令序列指定一个变量,便于重用。定义方式为:

define 变量名命令序列endef

  之后直接按变量引用方式($(变量名))引用即可。

  (4)系统变量

  MAKEFLAGS:保存make参数信息;

  MAKELEVEL:保存了当前makefile在make嵌套执行的调用层数;

  CFLAGS:环境变量,若系统环境中设置了该环境变量,则所有makefile文件都可以使用该变量设置的统一参数。若makefile文件中定义了该变量,将覆盖环境变量中的值。

  MAKECMDGOALS:make的环境变量,这个变量中存放所指定的终极目标的列表,如果在命令行上,你没有指定目标,那么,这个变量是空值。

  2.3.3 伪目标

      “伪目标”并不是一个文件,只是一个标签。为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,如

.PHONY: cleanclean:rm *.o temp

      伪目标可以作为默认目标也可以有依赖文件,借由这个特性,我们可以一次性生成多个可执行文件。

 

2.4 表达式

         2.4.1 赋值表达式

         ?=:用于变量定义,表示如果变量没有被定义,则定义并赋值,如果定义了语句作废。

         +=:用于变量赋值时的追加,如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符(=或:=)。

         2.4.2 条件表达式

格式为:<conditional-directive><text-if-true>else<text-if-false>endif

  其中<conditional-directive>表示条件关键字。这个关键字有四个:ifeq、ifneq、ifdef、ifndef。     

 

2.5 函数

  2.5.1 调用语法

  

${<function> <arguments>}

  说明:<function>就是函数名,make 支持的函数不多。<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。

  2.5.2 常用函数

类别

函数名

功能

说明

字符串函数

$(subst <from>,<to>,<text> )

把字串<text>中的<from>字符串替换成<to>。

函数返回被替换过后的字符串。

$(patsubst <pattern>,<replacement>,<text> )

查找<text>中的单词是否符合模式<pattern>,匹配则以<replacement>替换。

如果<replacement>中也包含%,那么这个“%”将是<pattern>中的那个“%”所代表的字串。

$(strip <string> )

去掉<string>字串中开头和结尾的空字符。

函数返回被替换过后的字符串。

$(findstring <find>,<in> )

在字串<in>中查找<find>字串。

如果找到,返回<find>,否则返回空字符串。

$(filter <pattern...>,<text> )

以<pattern>模式过滤<text>字符串中的单词,保留符合<pattern>的单词。

可以有多个模式。

$(filter-out <pattern...>,<text> )

以<pattern>模式过滤<text>字符串中的单词,去除符合模式的单词

可以有多个模式。返回不符合模式<pattern>的字串。

$(sort <list> )

给字符串<list>中的单词排序(升序) 。

sort 函数会去掉<list>中相同的单词。

$(word <n>,<text> )

取字符串<text>中第<n>个单词。 (从一开始)

如果<n>比<text>中的单词数要大,返回空字符串。

$(wordlist <s>,<e>,<text> )

从字符串<text>中取从<s>开始到<e>的单词串。

如果<s>比<text>中的单词数要大,返回空字符串。

$(words <text> )

统计<text>中字符串中的单词个数。

取最后一个单词: $(word

$(words <text> ),<text> )。

$(firstword <text> )

取字符串<text>中的第一个单词。

 

文件操作函数

$(dir <names...> )

从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。

如果没有反斜杠,那么返回“./”

$(notdir <names...> )

从文件名序列<names>中取出非目录部分。

非目录部分是指最后一个反斜杠(“/”)之后的部分。

$(suffix <names...> )

从文件名序列<names>中取出各个文件名的后缀。

如果文件没有后缀,则返回空字串。

$(basename <names...> )

从文件名序列<names>中取出各个文件名的前缀部分。

 

$(addsuffix <suffix>,<names...> )

把后缀<suffix>加到<names>中的每个单词后面。

 

$(addprefix <prefix>,<names...> )

把前缀<prefix>加到<names>中的每个单词后面。

 

$(join <list1>,<list2> )

把<list2>中的单词对应地加到<list1>的单词后面。

如果<list1>的单词个数多于<list2>,多出部分保持原样。反之复制到1中。

循环

$(foreach <var>,<list>,<text> )

把<list>中的单词逐一取出到<var>变量中,再执行<text>包含的表达式。

返回值为text每次执行返回的字符串组合(以空格相隔)

条件

$(if <condition>,<then-part>,<else-part> )

 

 

 call  

$(call <expression>,<parm1>,<parm2>,<parm3>...)

<expression>中的变量($(1),$(2)…)会 被 <parm1> , <parm2>…依 次 取 代 。

返回值为<expression>的返回值

 origin

$(origin <variable> )

返回变量来源

返回值为:undefined,default,environment,file,command line,override,automatic

Shell函数

$(shell 命令)

它的参数是操作系统Shell 的命令

返回值为shell函数所执行的操作系统命令的输出

控制make的函数

$(error <text ...> )

产生一个致命的错误,<text ...>是错误信息。

 

$(warning <text ...> )

输出一段警告信息, make 继续执行。

 

 

2.6 make

         2.6.1 指定makefile和目标

         (1)指定makefile

make 命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。若找不到,将会依次到make命令-I参数指定目录和<prefix>/include(一般是:/usr/local/bin 或/usr/include)目录查找。如果要指定特定的 Makefile,可以使用 make 的“-f”和“--file”参数,如:make -f Make.Linux。

         (2)指定目标

         一般来说,make 的最终目标是 makefile 中的第一个目标,而其它目标一般是由这个目标连带出来的。而要指定其他目标作为最终目标,在 make 命令后直接跟目标的名字就可以完成(如前面提到的“makeclean”形式),即使是伪目标和隐含目标都可以作为最终目标。

         GNU目标编写习惯说明:

伪目标

功能

all

所有目标的目标,其功能一般是编译所有的目标。

clean

删除所有被 make 创建的文件。

Install

安装已编译好的程序,就是把目标执行文件拷贝到指定的目标中去。

print

列出改变过的源文件。

tar

把源程序打包备份,也就是一个tar文件。

dist

创建一个压缩文件,一般是把 tar 文件压成Z文件或是gz文件。

TAGS

更新所有的目标,以备完整地重编译使用。

check、test

测试 makefile 的流程。

         2.6.2 make的参数

  下面列举了所有 GNU make 3.80 版的参数定义:

参数

功能

-b,-m

忽略和其它版本 make 的兼容性。

-B,--always-make

认为所有的目标都需要更新(重编译) 。

-C <dir>, --directory=<dir>

指定读取 makefile 的目录。如果有多个“-C”参数,后面的路径以前面的作为相对路径,并以最后目录作被指定目录。

—debug[=<options>]

输出 make 的调试信息,它有几种不同的级别可供选择。

-e, --environment-overrides

指明环境变量的值覆盖 makefile 中定义的变量的值。

-f=<file>

指定需要执行的 makefile。

-h

 

-i

在执行时忽略所有的错误

-I <dir>

指定一个被包含 makefile 的搜索目标。

-k

出错也不停止运行。

-l <load>

指定 make 运行命令的负载。

-n

仅输出执行过程中的命令序列

-o <file>

不重新生成的指定的<file>

-p

输出 makefile 中的所有数据,包括所有的规则和变量。

-q

不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。

……

 

         详细参数信息可以查看make帮助文档。

         2.6.3 使用make更新函数库文件

         函数库文件也就是对 Object 文件(程序编译的中间文件)打包生成的文件。可以以如下格式指定函数库文件及其组成:

${<function> <arguments>}

  这不是一个命令,而是一个目标和依赖的定义。如果要指定多个 member,那就以空格分开,如:foolib(hack.o kludge.o)。

 

2.7其他

  (1)显示命令:在makefile中的命令前加@符号,则该命令在make执行时将不被显示在屏幕上;而在make命令中使用参数-n或--just-print,则只在屏幕显示执行的命令序列,而不会执行命令,常用来调试。

  (2)环境变量MAKEFILES:类似于include(filename),但环境变量MAKEFILES中的目标(即文件中的第一个标识符)不起作用,且不理会其中定义的文件的错误。建议不要使用该环境变量,容易出一些莫名的错误。

  (3)make 是在读取 Makefile 时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。

  (4)make的嵌套执行:大型工程为了利于模块编译和维护,通常会在每一个模块目录中分别写上makefile文件,而在总控makefile文件中指定模块makefile的执行。而总控模块中的变量可以通过声明export variable传递到下级的makefile中,但不会覆盖下级makefile中的变量,直接使用export表示传递所有变量。其中变量SHELL和MAKEFLAGS总是会自动传递到下层makefile中,而MAKEFLAGS中保存了make参数信息,一旦定义了MAKEFLAGS变量,它将自动传递到下级makefile中(除了几个参数:C,f,h,o,W),若不想传递make参数,可以这样:

subsystem:cd subdir && $(MAKE) MAKEFLAGS=

         (5)命令出错:make会检查每条命令执行后的返回值(),如果不正确则会终止该条规则的执行。1如果不希望命令出错而终止规则运行,可以在命令前加一个“-”表示忽略该命令出错继续执行后续命令;2也可以指定make命令参数-i或—ignore-errors表示忽略所有命令的错误;3而如果一个规则的目标是.IGNORE,则表示该条规则的所有命令都忽略错误。4而如果指定make的参数-k或—keep-going,则表示命令出错时终止对应规则而继续执行其他规则。

 

三、例子

  例1 多目标示例1

bigoutput littleoutput : text.ggenerate text.g -$(subst output,,$@) > $@

  说明:其中,-$(subst output,,$@)中的“$”表示执行一个 Makefile 的函数,函数名为 subst,后面的为参数。这里的这个函数是截取字符串的意思,“$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。

  例2 多目标示例2

objects = foo.o bar.oall: $(objects)$(objects): %.o: %.c$(CC) -c $(CFLAGS) $< -o $@

  说明:上面的例子中,指明了我们的目标从$object 中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object 集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“$<”和“$@”则是自动化变量, “$<”表示所有的依赖目标集(也就是“foo.c bar.c”) ,“$@”表示目标集(也就是“foo.o bar.o”)。

例3 生成.d文件(源文件的依赖关系文件)的模式规则:

%.d: %.c@set -e; rm -f $@; \$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \rm -f $@.$$$$

  说明:这个规则的意思是,所有的[.d]文件依赖于[.c]文件,“rm -f $@”的意思是删除所有的目标,也就是[.d]文件,第二行的意思是,为每个依赖文件“$<”, 也就是[.c]文件生成依赖文件, “$@”表示模式“%.d”文件,如果有一个 C 文件是 name.c,那么“%”就是“name”,“$$$$”意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使用sed 命令做了一个替换,关于 sed 命令的用法请参看相关的使用文档。第四行就是删除临时文件。