﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-茶博-随笔分类-编译器相关</title><link>http://www.cppblog.com/yehongly/category/8291.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 30 Oct 2008 01:53:54 GMT</lastBuildDate><pubDate>Thu, 30 Oct 2008 01:53:54 GMT</pubDate><ttl>60</ttl><item><title>写makefile——三</title><link>http://www.cppblog.com/yehongly/archive/2008/09/18/62200.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Thu, 18 Sep 2008 09:44:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/09/18/62200.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/62200.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/09/18/62200.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/62200.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/62200.html</trackback:ping><description><![CDATA[<p><font size="4" face="Courier New"><strong>使用变量<br>————</strong></font></p>
<p><font face="Courier New">在Makefile中的定义的变量，就像是C/C++语言中的宏一样，他代表了一个文本字串，
在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是，你可以在Makefile中改变其值。在
Makefile中，变量可以使用在&#8220;目标&#8221;，&#8220;依赖目标&#8221;，&#8220;命令&#8221;或是Makefile的其它部分中。</font></p>
<p><font face="Courier New">变量的命名字可以包含字符、数字，下划线（可以是数字开头），但不应该含有&#8220;:&#8221;、&#8220;#&#8221;、
&#8220;=&#8221;或是空字符（空格、回车等）。变量是大小写敏感的，&#8220;foo&#8221;、&#8220;Foo&#8221;和&#8220;FOO&#8221;是三个不同的变量名。传统的Makefile的变量名是全大
写的命名方式，但我推荐使用大小写搭配的变量名，如：MakeFlags。这样可以避免和系统的变量冲突，而发生意外的事情。</font></p>
<p><font face="Courier New">有一些变量是很奇怪字串，如&#8220;$&lt;&#8221;、&#8220;$@&#8221;等，这些是自动化变量，我会在后面介绍。</font></p>
<p><font face="Courier New"><strong>一、变量的基础</strong></font></p>
<p><font face="Courier New">变量在声明时需要给予初值，而在使用时，需要给在变量名前加上&#8220;$&#8221;符号，但最好用小括号&#8220;（）&#8221;或是大括号&#8220;{}&#8221;把变量给包括起来。如果你要使用真实的&#8220;$&#8221;字符，那么你需要用&#8220;$$&#8221;来表示。</font></p>
<p><font face="Courier New">变量可以使用在许多地方，如规则中的&#8220;目标&#8221;、&#8220;依赖&#8221;、&#8220;命令&#8221;以及新的变量中。先看一个例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; objects = program.o foo.o utils.o<br>&nbsp;&nbsp;&nbsp; program : $(objects)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o program $(objects)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; $(objects) : defs.h</font></p>
<p><font face="Courier New">变量会在使用它的地方精确地展开，就像C/C++中的宏一样，例如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo = c<br>&nbsp;&nbsp;&nbsp; prog.o : prog.$(foo)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(foo)$(foo) -$(foo) prog.$(foo)</font></p>
<p><font face="Courier New">展开后得到：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; prog.o : prog.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c prog.c</font></p>
<p><font face="Courier New">当然，千万不要在你的Makefile中这样干，这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个&#8220;替代&#8221;的原理。</font></p>
<p><font face="Courier New">另外，给变量加上括号完全是为了更加安全地使用这个变量，在上面的例子中，如果你不想给变量加上括号，那也可以，但我还是强烈建议你给变量加上括号。</font></p>
<p><br><font face="Courier New"><strong>二、变量中的变量</strong></font></p>
<p><font face="Courier New">在定义变量的值时，我们可以使用其它变量来构造变量的值，在Makefile中有两种方式来在用变量定义变量的值。</font></p>
<p><font face="Courier New">先看第一种方式，也就是简单的使用&#8220;=&#8221;号，在&#8220;=&#8221;左侧是变量，右侧是变量的值，右侧变量的值可以定义在文件的任何一处，也就是说，右侧中的变量不一定非要是已定义好的值，其也可以使用后面定义的值。如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo = $(bar)<br>&nbsp;&nbsp;&nbsp; bar = $(ugh)<br>&nbsp;&nbsp;&nbsp; ugh = Huh?</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; all:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; echo $(foo)</font></p>
<p><font face="Courier New">我们执行&#8220;make all&#8221;将会打出变量$(foo)的值是&#8220;Huh?&#8221;（ $(foo)的值是$(bar)，$(bar)的值是$(ugh)，$(ugh)的值是&#8220;Huh?&#8221;）可见，变量是可以使用后面的变量来定义的。</font></p>
<p><font face="Courier New">这个功能有好的地方，也有不好的地方，好的地方是，我们可以把变量的真实值推到后面来定义，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CFLAGS = $(include_dirs) -O<br>&nbsp;&nbsp;&nbsp; include_dirs = -Ifoo -Ibar</font></p>
<p><font face="Courier New">当&#8220;CFLAGS&#8221;在命令中被展开时，会是&#8220;-Ifoo -Ibar -O&#8221;。但这种形式也有不好的地方，那就是递归定义，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CFLAGS = $(CFLAGS) -O</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 或：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; A = $(B)<br>&nbsp;&nbsp;&nbsp; B = $(A)</font></p>
<p><font face="Courier New">这会让make陷入无限的变量展开过程中去，当然，我们的make是有能力检测这样的定义，并
会报错。还有就是如果在变量中使用函数，那么，这种方式会让我们的make运行时非常慢，更糟糕的是，他会使用得两个make的函数&#8220;wildcard&#8221;
和&#8220;shell&#8221;发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。</font></p>
<p><font face="Courier New">为了避免上面的这种方法，我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是&#8220;:=&#8221;操作符，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; x := foo<br>&nbsp;&nbsp;&nbsp; y := $(x) bar<br>&nbsp;&nbsp;&nbsp; x := later</font></p>
<p><font face="Courier New">其等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; y := foo bar<br>&nbsp;&nbsp;&nbsp; x := later</font></p>
<p><font face="Courier New">值得一提的是，这种方法，前面的变量不能使用后面的变量，只能使用前面已定义好了的变量。如果是这样：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; y := $(x) bar<br>&nbsp;&nbsp;&nbsp; x := foo</font></p>
<p><font face="Courier New">那么，y的值是&#8220;bar&#8221;，而不是&#8220;foo bar&#8221;。</font></p>
<p><font face="Courier New">上面都是一些比较简单的变量使用了，让我们来看一个复杂的例子，其中包括了make的函数、条件表达式和一个系统变量&#8220;MAKELEVEL&#8221;的使用：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifeq (0,${MAKELEVEL})<br>&nbsp;&nbsp;&nbsp; cur-dir&nbsp;&nbsp; := $(shell pwd)<br>&nbsp;&nbsp;&nbsp; whoami&nbsp;&nbsp;&nbsp; := $(shell whoami)<br>&nbsp;&nbsp;&nbsp; host-type := $(shell arch)<br>&nbsp;&nbsp;&nbsp; MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">关于条件表达式和函数，我们在后面再说，对于系统变量&#8220;MAKELEVEL&#8221;，其意思是，如果我们的make有一个嵌套执行的动作（参见前面的&#8220;嵌套使用make&#8221;），那么，这个变量会记录了我们的当前Makefile的调用层数。</font></p>
<p><font face="Courier New">下面再介绍两个定义变量时我们需要知道的，请先看一个例子，如果我们要定义一个变量，其值是一个空格，那么我们可以这样来：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; nullstring :=<br>&nbsp;&nbsp;&nbsp; space := $(nullstring) # end of the line</font></p>
<p><font face="Courier New">nullstring是一个Empty变量，其中什么也没有，而我们的space的值是一个空
格。因为在操作符的右边是很难描述一个空格的，这里采用的技术很管用，先用一个Empty变量来标明变量的值开始了，而后面采用&#8220;#&#8221;注释符来表示变量定
义的终止，这样，我们可以定义出其值是一个空格的变量。请注意这里关于&#8220;#&#8221;的使用，注释符&#8220;#&#8221;的这种特性值得我们注意，如果我们这样定义一个变量：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; dir := /foo/bar&nbsp;&nbsp;&nbsp; # directory to put the frobs in</font></p>
<p><font face="Courier New">dir这个变量的值是&#8220;/foo/bar&#8221;，后面还跟了4个空格，如果我们这样使用这样变量来指定别的目录——&#8220;$(dir)/file&#8221;那么就完蛋了。</font></p>
<p><font face="Courier New">还有一个比较有用的操作符是&#8220;?=&#8221;，先看示例：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; FOO ?= bar</font></p>
<p><font face="Courier New">其含义是，如果FOO没有被定义过，那么变量FOO的值就是&#8220;bar&#8221;，如果FOO先前被定义过，那么这条语将什么也不做，其等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifeq ($(origin FOO), undefined)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FOO = bar<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><br><font face="Courier New"><strong>三、变量高级用法</strong></font></p>
<p><font face="Courier New">这里介绍两种变量的高级使用方法，第一种是变量值的替换。</font></p>
<p><font face="Courier New">我们可以替换变量中的共有的部分，其格式是&#8220;$(var:a=b)&#8221;或是&#8220;${var:a=b}&#8221;，其意思是，把变量&#8220;var&#8221;中所有以&#8220;a&#8221;字串&#8220;结尾&#8221;的&#8220;a&#8221;替换成&#8220;b&#8221;字串。这里的&#8220;结尾&#8221;意思是&#8220;空格&#8221;或是&#8220;结束符&#8221;。</font></p>
<p><font face="Courier New">还是看一个示例吧：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo := a.o b.o c.o<br>&nbsp;&nbsp;&nbsp; bar := $(foo:.o=.c)</font></p>
<p><font face="Courier New">这个示例中，我们先定义了一个&#8220;$(foo)&#8221;变量，而第二行的意思是把&#8220;$(foo)&#8221;中所有以&#8220;.o&#8221;字串&#8220;结尾&#8221;全部替换成&#8220;.c&#8221;，所以我们的&#8220;$(bar)&#8221;的值就是&#8220;a.c b.c c.c&#8221;。</font></p>
<p><font face="Courier New">另外一种变量替换的技术是以&#8220;静态模式&#8221;（参见前面章节）定义的，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo := a.o b.o c.o<br>&nbsp;&nbsp;&nbsp; bar := $(foo:%.o=%.c)</font></p>
<p><font face="Courier New">这依赖于被替换字串中的有相同的模式，模式中必须包含一个&#8220;%&#8221;字符，这个例子同样让$(bar)变量的值为&#8220;a.c b.c c.c&#8221;。 </font></p>
<p><font face="Courier New">第二种高级用法是——&#8220;把变量的值再当成变量&#8221;。先看一个例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; x = y<br>&nbsp;&nbsp;&nbsp; y = z<br>&nbsp;&nbsp;&nbsp; a := $($(x))</font></p>
<p><font face="Courier New">在这个例子中，$(x)的值是&#8220;y&#8221;，所以$($(x))就是$(y)，于是$(a)的值就是&#8220;z&#8221;。（注意，是&#8220;x=y&#8221;，而不是&#8220;x=$(y)&#8221;）</font></p>
<p><font face="Courier New">我们还可以使用更多的层次：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; x = y<br>&nbsp;&nbsp;&nbsp; y = z<br>&nbsp;&nbsp;&nbsp; z = u<br>&nbsp;&nbsp;&nbsp; a := $($($(x)))</font></p>
<p><font face="Courier New">这里的$(a)的值是&#8220;u&#8221;，相关的推导留给读者自己去做吧。</font></p>
<p><font face="Courier New">让我们再复杂一点，使用上&#8220;在变量定义中使用变量&#8221;的第一个方式，来看一个例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; x = $(y)<br>&nbsp;&nbsp;&nbsp; y = z<br>&nbsp;&nbsp;&nbsp; z = Hello<br>&nbsp;&nbsp;&nbsp; a := $($(x))</font></p>
<p><font face="Courier New">这里的$($(x))被替换成了$($(y))，因为$(y)值是&#8220;z&#8221;，所以，最终结果是：a:=$(z)，也就是&#8220;Hello&#8221;。</font></p>
<p><font face="Courier New">再复杂一点，我们再加上函数：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; x = variable1<br>&nbsp;&nbsp;&nbsp; variable2 := Hello<br>&nbsp;&nbsp;&nbsp; y = $(subst 1,2,$(x))<br>&nbsp;&nbsp;&nbsp; z = y<br>&nbsp;&nbsp;&nbsp; a := $($($(z)))</font></p>
<p><font face="Courier New">这个例子中，&#8220;$($($(z)))&#8221;扩展为&#8220;$($(y))&#8221;，而其再次被扩展为
&#8220;$($(subst
1,2,$(x)))&#8221;。$(x)的值是&#8220;variable1&#8221;，subst函数把&#8220;variable1&#8221;中的所有&#8220;1&#8221;字串替换成&#8220;2&#8221;字串，于
是，&#8220;variable1&#8221;变成&#8220;variable2&#8221;，再取其值，所以，最终，$(a)的值就是$(variable2)的值——
&#8220;Hello&#8221;。（喔，好不容易）</font></p>
<p><font face="Courier New">在这种方式中，或要可以使用多个变量来组成一个变量的名字，然后再取其值：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; first_second = Hello<br>&nbsp;&nbsp;&nbsp; a = first<br>&nbsp;&nbsp;&nbsp; b = second<br>&nbsp;&nbsp;&nbsp; all = $($a_$b)</font></p>
<p><font face="Courier New">这里的&#8220;$a_$b&#8221;组成了&#8220;first_second&#8221;，于是，$(all)的值就是&#8220;Hello&#8221;。</font></p>
<p><font face="Courier New">再来看看结合第一种技术的例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; a_objects := a.o b.o c.o<br>&nbsp;&nbsp;&nbsp; 1_objects := 1.o 2.o 3.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; sources := $($(a1)_objects:.o=.c)</font></p>
<p><font face="Courier New">这个例子中，如果$(a1)的值是&#8220;a&#8221;的话，那么，$(sources)的值就是&#8220;a.c b.c c.c&#8221;；如果$(a1)的值是&#8220;1&#8221;，那么$(sources)的值是&#8220;1.c 2.c 3.c&#8221;。</font></p>
<p><font face="Courier New">再来看一个这种技术和&#8220;函数&#8221;与&#8220;条件语句&#8221;一同使用的例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifdef do_sort<br>&nbsp;&nbsp;&nbsp; func := sort<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; func := strip<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; bar := a d b g q c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo := $($(func) $(bar))</font></p>
<p><font face="Courier New">这个示例中，如果定义了&#8220;do_sort&#8221;，那么：foo := $(sort a d b
g q c)，于是$(foo)的值就是&#8220;a b c d g q&#8221;，而如果没有定义&#8220;do_sort&#8221;，那么：foo := $(sort a d
b g q c)，调用的就是strip函数。</font></p>
<p><font face="Courier New">当然，&#8220;把变量的值再当成变量&#8221;这种技术，同样可以用在操作符的左边：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; dir = foo<br>&nbsp;&nbsp;&nbsp; $(dir)_sources := $(wildcard $(dir)/*.c)<br>&nbsp;&nbsp;&nbsp; define $(dir)_print<br>&nbsp;&nbsp;&nbsp; lpr $($(dir)_sources)<br>&nbsp;&nbsp;&nbsp; endef</font></p>
<p><font face="Courier New">这个例子中定义了三个变量：&#8220;dir&#8221;，&#8220;foo_sources&#8221;和&#8220;foo_print&#8221;。</font></p>
<p><br><font face="Courier New"><strong>四、追加变量值</strong></font></p>
<p><font face="Courier New">我们可以使用&#8220;+=&#8221;操作符给变量追加值，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; objects = main.o foo.o bar.o utils.o<br>&nbsp;&nbsp;&nbsp; objects += another.o</font></p>
<p><font face="Courier New">于是，我们的$(objects)值变成：&#8220;main.o foo.o bar.o utils.o another.o&#8221;（another.o被追加进去了）</font></p>
<p><font face="Courier New">使用&#8220;+=&#8221;操作符，可以模拟为下面的这种例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; objects = main.o foo.o bar.o utils.o<br>&nbsp;&nbsp;&nbsp; objects := $(objects) another.o</font></p>
<p><font face="Courier New">所不同的是，用&#8220;+=&#8221;更为简洁。</font></p>
<p><font face="Courier New">如果变量之前没有定义过，那么，&#8220;+=&#8221;会自动变成&#8220;=&#8221;，如果前面有变量定义，那么&#8220;+=&#8221;会继承于前次操作的赋值符。如果前一次的是&#8220;:=&#8221;，那么&#8220;+=&#8221;会以&#8220;:=&#8221;作为其赋值符，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; variable := value<br>&nbsp;&nbsp;&nbsp; variable += more</font></p>
<p><font face="Courier New">等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; variable := value<br>&nbsp;&nbsp;&nbsp; variable := $(variable) more</font></p>
<p><font face="Courier New">但如果是这种情况： </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; variable = value<br>&nbsp;&nbsp;&nbsp; variable += more</font></p>
<p><font face="Courier New">由于前次的赋值符是&#8220;=&#8221;，所以&#8220;+=&#8221;也会以&#8220;=&#8221;来做为赋值，那么岂不会发生变量的递补归定义，这是很不好的，所以make会自动为我们解决这个问题，我们不必担心这个问题。</font></p>
<p><br><font face="Courier New"><strong>五、override 指示符</strong></font></p>
<p><font face="Courier New">如果有变量是通常make的命令行参数设置的，那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值，那么，你可以使用&#8220;override&#8221;指示符。其语法是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; override &lt;variable&gt; = &lt;value&gt;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; override &lt;variable&gt; := &lt;value&gt;</font></p>
<p><font face="Courier New">当然，你还可以追加：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; override &lt;variable&gt; += &lt;more text&gt;</font></p>
<p><font face="Courier New">对于多行的变量定义，我们用define指示符，在define指示符前，也同样可以使用ovveride指示符，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; override define foo<br>&nbsp;&nbsp;&nbsp; bar<br>&nbsp;&nbsp;&nbsp; endef</font></p>
<p><font face="Courier New"><strong>六、多行变量</strong><br>&nbsp;<br>还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行，这有利于定义一系列的命令（前面我们讲过&#8220;命令包&#8221;的技术就是利用这个关键字）。</font></p>
<p><font face="Courier New">define指示符后面跟的是变量的名字，而重起一行定义变量的值，定义是以endef关键字
结束。其工作方式和&#8220;=&#8221;操作符一样。变量的值可以包含函数、命令、文字，或是其它变量。因为命令需要以[Tab]键开头，所以如果你用define定义
的命令变量中没有以[Tab]键开头，那么make就不会把其认为是命令。</font></p>
<p><font face="Courier New">下面的这个示例展示了define的用法：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; define two-lines<br>&nbsp;&nbsp;&nbsp; echo foo<br>&nbsp;&nbsp;&nbsp; echo $(bar)<br>&nbsp;&nbsp;&nbsp; endef</font></p>
<p><br><font face="Courier New"><strong>七、环境变量</strong></font></p>
<p><font face="Courier New">make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中，但是
如果Makefile中已定义了这个变量，或是这个变量由make命令行带入，那么系统的环境变量的值将被覆盖。（如果make指定了&#8220;-e&#8221;参数，那
么，系统环境变量将覆盖Makefile中定义的变量）</font></p>
<p><font face="Courier New">因此，如果我们在环境变量中设置了&#8220;CFLAGS&#8221;环境变量，那么我们就可以在所有的
Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS，那么则会使用
Makefile中的这个变量，如果没有定义则使用系统环境变量的值，一个共性和个性的统一，很像&#8220;全局变量&#8221;和&#8220;局部变量&#8221;的特性。</font></p>
<p><font face="Courier New">当make嵌套调用时（参见前面的&#8220;嵌套调用&#8221;章节），上层Makefile中定义的变量会以
系统环境变量的方式传递到下层的Makefile中。当然，默认情况下，只有通过命令行设置的变量会被传递。而定义在文件中的变量，如果要向下层
Makefile传递，则需要使用exprot关键字来声明。（参见前面章节）</font></p>
<p><font face="Courier New">当然，我并不推荐把许多的变量都定义在系统环境中，这样，在我们执行不用的Makefile时，拥有的是同一套系统变量，这可能会带来更多的麻烦。</font></p>
<p><br><font face="Courier New"><strong>八、目标变量</strong></font></p>
<p><font face="Courier New">前面我们所讲的在Makefile中定义的变量都是&#8220;全局变量&#8221;，在整个文件，我们都可以访问这些变量。当然，&#8220;自动化变量&#8221;除外，如&#8220;$&lt;&#8221;等这种类量的自动化变量就属于&#8220;规则型变量&#8221;，这种变量的值依赖于规则的目标和依赖目标的定义。</font></p>
<p><font face="Courier New">当然，我样同样可以为某个目标设置局部变量，这种变量被称为&#8220;Target-specific Variable&#8221;，它可以和&#8220;全局变量&#8221;同名，因为它的作用范围只在这条规则以及连带规则中，所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。</font></p>
<p><font face="Courier New">其语法是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &lt;target ...&gt; : &lt;variable-assignment&gt;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &lt;target ...&gt; : overide &lt;variable-assignment&gt;</font></p>
<p><font face="Courier New">&lt;variable-assignment&gt;可以是前面讲过的各种赋值表达式，如&#8220;=&#8221;、&#8220;:=&#8221;、&#8220;+=&#8221;或是&#8220;？=&#8221;。第二个语法是针对于make命令行带入的变量，或是系统环境变量。</font></p>
<p><font face="Courier New">这个特性非常的有用，当我们设置了这样一个变量，这个变量会作用到由这个目标所引发的所有的规则中去。如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; prog : CFLAGS = -g<br>&nbsp;&nbsp;&nbsp; prog : prog.o foo.o bar.o<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) $(CFLAGS) prog.o foo.o bar.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; prog.o : prog.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) $(CFLAGS) prog.c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo.o : foo.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) $(CFLAGS) foo.c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; bar.o : bar.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) $(CFLAGS) bar.c<br>&nbsp;<br>在这个示例中，不管全局的$(CFLAGS)的值是什么，在prog目标，以及其所引发的所有规则中（prog.o foo.o bar.o的规则），$(CFLAGS)的值都是&#8220;-g&#8221;</font></p>
<p><br><font face="Courier New"><strong>九、模式变量</strong></font></p>
<p><font face="Courier New">在GNU的make中，还支持模式变量（Pattern-specific Variable），通过上面的目标变量中，我们知道，变量可以定义在某个目标上。模式变量的好处就是，我们可以给定一种&#8220;模式&#8221;，可以把变量定义在符合这种模式的所有目标上。</font></p>
<p><font face="Courier New">我们知道，make的&#8220;模式&#8221;一般是至少含有一个&#8220;%&#8221;的，所以，我们可以以如下方式给所有以[.o]结尾的目标定义目标变量：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; %.o : CFLAGS = -O</font></p>
<p><font face="Courier New">同样，模式变量的语法和&#8220;目标变量&#8221;一样：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &lt;pattern ...&gt; : &lt;variable-assignment&gt;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &lt;pattern ...&gt; : override &lt;variable-assignment&gt;</font></p>
<p><font face="Courier New">override同样是针对于系统环境传入的变量，或是make命令行指定的变量。<br>&nbsp;</font></p>
<p><br><font size="4" face="Courier New"><strong>使用条件判断<br>——————</strong></font></p>
<p><font face="Courier New">使用条件判断，可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值，或是比较变量和常量的值。</font></p>
<p><font face="Courier New"><strong>一、示例</strong></font></p>
<p><font face="Courier New">下面的例子，判断$(CC)变量是否&#8220;gcc&#8221;，如果是的话，则使用GNU函数编译目标。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; libs_for_gcc = -lgnu<br>&nbsp;&nbsp;&nbsp; normal_libs =</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo: $(objects)<br>&nbsp;&nbsp;&nbsp; ifeq ($(CC),gcc)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -o foo $(objects) $(libs_for_gcc)<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -o foo $(objects) $(normal_libs)<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">可见，在上面示例的这个规则中，目标&#8220;foo&#8221;可以根据变量&#8220;$(CC)&#8221;值来选取不同的函数库来编译程序。</font></p>
<p><font face="Courier New">我们可以从上面的示例中看到三个关键字：ifeq、else和endif。ifeq的意思表示
条件语句的开始，并指定一个条件表达式，表达式包含两个参数，以逗号分隔，表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条
件语句的结束，任何一个条件表达式都应该以endif结束。</font></p>
<p><font face="Courier New">当我们的变量$(CC)值是&#8220;gcc&#8221;时，目标foo的规则是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo: $(objects)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -o foo $(objects) $(libs_for_gcc)</font></p>
<p><font face="Courier New">而当我们的变量$(CC)值不是&#8220;gcc&#8221;时（比如&#8220;cc&#8221;），目标foo的规则是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo: $(objects)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -o foo $(objects) $(normal_libs)</font></p>
<p><font face="Courier New">当然，我们还可以把上面的那个例子写得更简洁一些：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; libs_for_gcc = -lgnu<br>&nbsp;&nbsp;&nbsp; normal_libs =</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifeq ($(CC),gcc)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libs=$(libs_for_gcc)<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libs=$(normal_libs)<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo: $(objects)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -o foo $(objects) $(libs)</font></p>
<p><br><font face="Courier New"><strong>二、语法</strong></font></p>
<p><font face="Courier New">条件表达式的语法为：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &lt;conditional-directive&gt;<br>&nbsp;&nbsp;&nbsp; &lt;text-if-true&gt;<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">以及：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &lt;conditional-directive&gt;<br>&nbsp;&nbsp;&nbsp; &lt;text-if-true&gt;<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; &lt;text-if-false&gt;<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">其中&lt;conditional-directive&gt;表示条件关键字，如&#8220;ifeq&#8221;。这个关键字有四个。</font></p>
<p><font face="Courier New">第一个是我们前面所见过的&#8220;ifeq&#8221;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifeq (&lt;arg1&gt;, &lt;arg2&gt;) <br>&nbsp;&nbsp;&nbsp; ifeq '&lt;arg1&gt;' '&lt;arg2&gt;' <br>&nbsp;&nbsp;&nbsp; ifeq "&lt;arg1&gt;" "&lt;arg2&gt;" <br>&nbsp;&nbsp;&nbsp; ifeq "&lt;arg1&gt;" '&lt;arg2&gt;' <br>&nbsp;&nbsp;&nbsp; ifeq '&lt;arg1&gt;' "&lt;arg2&gt;" </font></p>
<p><font face="Courier New">比较参数&#8220;arg1&#8221;和&#8220;arg2&#8221;的值是否相同。当然，参数中我们还可以使用make的函数。如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifeq ($(strip $(foo)),)<br>&nbsp;&nbsp;&nbsp; &lt;text-if-empty&gt;<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">这个示例中使用了&#8220;strip&#8221;函数，如果这个函数的返回值是空（Empty），那么&lt;text-if-empty&gt;就生效。</font></p>
<p><font face="Courier New">第二个条件关键字是&#8220;ifneq&#8221;。语法是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifneq (&lt;arg1&gt;, &lt;arg2&gt;) <br>&nbsp;&nbsp;&nbsp; ifneq '&lt;arg1&gt;' '&lt;arg2&gt;' <br>&nbsp;&nbsp;&nbsp; ifneq "&lt;arg1&gt;" "&lt;arg2&gt;" <br>&nbsp;&nbsp;&nbsp; ifneq "&lt;arg1&gt;" '&lt;arg2&gt;' <br>&nbsp;&nbsp;&nbsp; ifneq '&lt;arg1&gt;' "&lt;arg2&gt;" </font></p>
<p><font face="Courier New">其比较参数&#8220;arg1&#8221;和&#8220;arg2&#8221;的值是否相同，如果不同，则为真。和&#8220;ifeq&#8221;类似。</font></p>
<p><font face="Courier New">第三个条件关键字是&#8220;ifdef&#8221;。语法是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifdef &lt;variable-name&gt; </font></p>
<p><font face="Courier New">如果变量&lt;variable-name&gt;的值非空，那到表达式为真。否则，表达式
为假。当然，&lt;variable-name&gt;同样可以是一个函数的返回值。注意，ifdef只是测试一个变量是否有值，其并不会把变量扩展到
当前位置。还是来看两个例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 示例一：<br>&nbsp;&nbsp;&nbsp; bar =<br>&nbsp;&nbsp;&nbsp; foo = $(bar)<br>&nbsp;&nbsp;&nbsp; ifdef foo<br>&nbsp;&nbsp;&nbsp; frobozz = yes<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; frobozz = no<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 示例二：<br>&nbsp;&nbsp;&nbsp; foo =<br>&nbsp;&nbsp;&nbsp; ifdef foo<br>&nbsp;&nbsp;&nbsp; frobozz = yes<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; frobozz = no<br>&nbsp;&nbsp;&nbsp; endif</font></p>
<p><font face="Courier New">第一个例子中，&#8220;$(frobozz)&#8221;值是&#8220;yes&#8221;，第二个则是&#8220;no&#8221;。</font></p>
<p><font face="Courier New">第四个条件关键字是&#8220;ifndef&#8221;。其语法是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ifndef &lt;variable-name&gt;</font></p>
<p><font face="Courier New">这个我就不多说了，和&#8220;ifdef&#8221;是相反的意思。</font></p>
<p><font face="Courier New">在&lt;conditional-directive&gt;这一行上，多余的空格是被允许的，但是不能以[Tab]键做为开始（不然就被认为是命令）。而注释符&#8220;#&#8221;同样也是安全的。&#8220;else&#8221;和&#8220;endif&#8221;也一样，只要不是以[Tab]键开始就行了。</font></p>
<p><font face="Courier New">特别注意的是，make是在读取Makefile时就计算条件表达式的值，并根据条件表达式的值来选择语句，所以，你最好不要把自动化变量（如&#8220;$@&#8221;等）放入条件表达式中，因为自动化变量是在运行时才有的。</font></p>
<p><font face="Courier New">而且，为了避免混乱，make不允许把整个条件语句分成两部分放在不同的文件中。</font></p>
<p><font size="3" face="Courier New"><strong>使用函数<br>————</strong></font></p>
<p><font face="Courier New">在Makefile中可以使用函数来处理变量，从而让我们的命令或是规则更为的灵活和具有智能。make所支持的函数也不算很多，不过已经足够我们的操作了。函数调用后，函数的返回值可以当做变量来使用。</font></p>
<p><br><font face="Courier New"><strong>一、函数的调用语法</strong></font></p>
<p><font face="Courier New">函数调用，很像变量的使用，也是以&#8220;$&#8221;来标识的，其语法如下：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; $(&lt;function&gt; &lt;arguments&gt;)</font></p>
<p><font face="Courier New">或是</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ${&lt;function&gt; &lt;arguments&gt;}</font></p>
<p><font face="Courier New">这里，&lt;function&gt;就是函数名，make支持的函数不
多。&lt;arguments&gt;是函数的参数，参数间以逗号&#8220;,&#8221;分隔，而函数名和参数之间以&#8220;空格&#8221;分隔。函数调用以&#8220;$&#8221;开头，以圆括号或花
括号把函数名和参数括起。感觉很像一个变量，是不是？函数中的参数可以使用变量，为了风格的统一，函数和变量的括号最好一样，如使用&#8220;$(subst
a,b,$(x))&#8221;这样的形式，而不是&#8220;$(subst a,b,${x})&#8221;的形式。因为统一会更清楚，也会减少一些不必要的麻烦。</font></p>
<p><font face="Courier New">还是来看一个示例：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; comma:= ,<br>&nbsp;&nbsp;&nbsp; empty:=<br>&nbsp;&nbsp;&nbsp; space:= $(empty) $(empty)<br>&nbsp;&nbsp;&nbsp; foo:= a b c<br>&nbsp;&nbsp;&nbsp; bar:= $(subst $(space),$(comma),$(foo))</font></p>
<p><font face="Courier New">在这个示例中，$(comma)的值是一个逗号。$(space)使用了$(empty)定义
了一个空格，$(foo)的值是&#8220;a b
c&#8221;，$(bar)的定义用，调用了函数&#8220;subst&#8221;，这是一个替换函数，这个函数有三个参数，第一个参数是被替换字串，第二个参数是替换字串，第三个
参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号，所以$(bar)的值是&#8220;a,b,c&#8221;。</font></p>
<p><br><font face="Courier New"><strong>二、字符串处理函数</strong></font></p>
<p><font face="Courier New"><strong>$(subst &lt;from&gt;,&lt;to&gt;,&lt;text&gt;)</strong> </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：字符串替换函数——subst。<br>&nbsp;&nbsp;&nbsp; 功能：把字串&lt;text&gt;中的&lt;from&gt;字符串替换成&lt;to&gt;。<br>&nbsp;&nbsp;&nbsp; 返回：函数返回被替换过后的字符串。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 示例：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(subst ee,EE,feet on the street)，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 把&#8220;feet on the street&#8221;中的&#8220;ee&#8221;替换成&#8220;EE&#8221;，返回结果是&#8220;fEEt on the strEEt&#8221;。</font></p>
<p><br><font face="Courier New"><strong>$(patsubst &lt;pattern&gt;,&lt;replacement&gt;,&lt;text&gt;)</strong> </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：模式字符串替换函数——patsubst。<br>&nbsp;&nbsp;&nbsp;
功能：查找&lt;text&gt;中的单词（单词以&#8220;空格&#8221;、&#8220;Tab&#8221;或&#8220;回车&#8221;&#8220;换行&#8221;分隔）是否符合模式&lt;pattern&gt;，如果匹
配的话，则以&lt;replacement&gt;替换。这里，&lt;pattern&gt;可以包括通配符&#8220;%&#8221;，表示任意长度的字串。如
果&lt;replacement&gt;中也包含&#8220;%&#8221;，那么，&lt;replacement&gt;中的这个&#8220;%&#8221;将
是&lt;pattern&gt;中的那个&#8220;%&#8221;所代表的字串。（可以用&#8220;\&#8221;来转义，以&#8220;\%&#8221;来表示真实含义的&#8220;%&#8221;字符）<br>&nbsp;&nbsp;&nbsp; 返回：函数返回被替换过后的字符串。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 示例：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(patsubst %.c,%.o,x.c.c bar.c)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 把字串&#8220;x.c.c bar.c&#8221;符合模式[%.c]的单词替换成[%.o]，返回结果是&#8220;x.c.o bar.o&#8221;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 备注：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这和我们前面&#8220;变量章节&#8221;说过的相关知识有点相似。如：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;$(var:&lt;pattern&gt;=&lt;replacement&gt;)&#8221;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 相当于<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;$(patsubst &lt;pattern&gt;,&lt;replacement&gt;,$(var))&#8221;，<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而&#8220;$(var: &lt;suffix&gt;=&lt;replacement&gt;)&#8221;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 则相当于<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;$(patsubst %&lt;suffix&gt;,%&lt;replacement&gt;,$(var))&#8221;。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 例如有：objects = foo.o bar.o baz.o，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 那么，&#8220;$(objects:.o=.c)&#8221;和&#8220;$(patsubst %.o,%.c,$(objects))&#8221;是一样的。</font></p>
<p><font face="Courier New"><strong>$(strip &lt;string&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：去空格函数——strip。<br>&nbsp;&nbsp;&nbsp; 功能：去掉&lt;string&gt;字串中开头和结尾的空字符。<br>&nbsp;&nbsp;&nbsp; 返回：返回被去掉空格的字符串值。<br>&nbsp;&nbsp;&nbsp; 示例：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(strip a b c )</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 把字串&#8220;a b c &#8221;去到开头和结尾的空格，结果是&#8220;a b c&#8221;。</font></p>
<p><font face="Courier New"><strong>$(findstring &lt;find&gt;,&lt;in&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：查找字符串函数——findstring。<br>&nbsp;&nbsp;&nbsp; 功能：在字串&lt;in&gt;中查找&lt;find&gt;字串。<br>&nbsp;&nbsp;&nbsp; 返回：如果找到，那么返回&lt;find&gt;，否则返回空字符串。<br>&nbsp;&nbsp;&nbsp; 示例：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(findstring a,a b c)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(findstring a,b c)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 第一个函数返回&#8220;a&#8221;字符串，第二个返回&#8220;&#8221;字符串（空字符串）</font></p>
<p><font face="Courier New"><strong>$(filter &lt;pattern...&gt;,&lt;text&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：过滤函数——filter。<br>&nbsp;&nbsp;&nbsp; 功能：以&lt;pattern&gt;模式过滤&lt;text&gt;字符串中的单词，保留符合模式&lt;pattern&gt;的单词。可以有多个模式。<br>&nbsp;&nbsp;&nbsp; 返回：返回符合模式&lt;pattern&gt;的字串。<br>&nbsp;&nbsp;&nbsp; 示例：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sources := foo.c bar.c baz.s ugh.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foo: $(sources)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc $(filter %.c %.s,$(sources)) -o foo</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(filter %.c %.s,$(sources))返回的值是&#8220;foo.c bar.c baz.s&#8221;。</font></p>
<p><font face="Courier New"><strong>$(filter-out &lt;pattern...&gt;,&lt;text&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：反过滤函数——filter-out。<br>&nbsp;&nbsp;&nbsp; 功能：以&lt;pattern&gt;模式过滤&lt;text&gt;字符串中的单词，去除符合模式&lt;pattern&gt;的单词。可以有多个模式。<br>&nbsp;&nbsp;&nbsp; 返回：返回不符合模式&lt;pattern&gt;的字串。<br>&nbsp;&nbsp;&nbsp; 示例：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; objects=main1.o foo.o main2.o bar.o<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mains=main1.o main2.o<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(filter-out $(mains),$(objects)) 返回值是&#8220;foo.o bar.o&#8221;。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br><strong>$(sort &lt;list&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：排序函数——sort。<br>&nbsp;&nbsp;&nbsp; 功能：给字符串&lt;list&gt;中的单词排序（升序）。<br>&nbsp;&nbsp;&nbsp; 返回：返回排序后的字符串。<br>&nbsp;&nbsp;&nbsp; 示例：$(sort foo bar lose)返回&#8220;bar foo lose&#8221; 。<br>&nbsp;&nbsp;&nbsp; 备注：sort函数会去掉&lt;list&gt;中相同的单词。</font></p>
<p><font face="Courier New"><strong>$(word &lt;n&gt;,&lt;text&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：取单词函数——word。<br>&nbsp;&nbsp;&nbsp; 功能：取字符串&lt;text&gt;中第&lt;n&gt;个单词。（从一开始）<br>&nbsp;&nbsp;&nbsp; 返回：返回字符串&lt;text&gt;中第&lt;n&gt;个单词。如果&lt;n&gt;比&lt;text&gt;中的单词数要大，那么返回空字符串。<br>&nbsp;&nbsp;&nbsp; 示例：$(word 2, foo bar baz)返回值是&#8220;bar&#8221;。</font></p>
<p><font face="Courier New"><strong>$(wordlist &lt;s&gt;,&lt;e&gt;,&lt;text&gt;)</strong>&nbsp; </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：取单词串函数——wordlist。<br>&nbsp;&nbsp;&nbsp; 功能：从字符串&lt;text&gt;中取从&lt;s&gt;开始到&lt;e&gt;的单词串。&lt;s&gt;和&lt;e&gt;是一个数字。<br>&nbsp;&nbsp;&nbsp;
返回：返回字符串&lt;text&gt;中从&lt;s&gt;到&lt;e&gt;的单词字串。如果&lt;s&gt;比&lt;text&gt;中
的单词数要大，那么返回空字符串。如果&lt;e&gt;大于&lt;text&gt;的单词数，那么返回从&lt;s&gt;开始，
到&lt;text&gt;结束的单词串。<br>&nbsp;&nbsp;&nbsp; 示例： $(wordlist 2, 3, foo bar baz)返回值是&#8220;bar baz&#8221;。</font></p>
<p><font face="Courier New"><strong>$(words &lt;text&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：单词个数统计函数——words。<br>&nbsp;&nbsp;&nbsp; 功能：统计&lt;text&gt;中字符串中的单词个数。<br>&nbsp;&nbsp;&nbsp; 返回：返回&lt;text&gt;中的单词数。<br>&nbsp;&nbsp;&nbsp; 示例：$(words, foo bar baz)返回值是&#8220;3&#8221;。<br>&nbsp;&nbsp;&nbsp; 备注：如果我们要取&lt;text&gt;中最后的一个单词，我们可以这样：$(word $(words &lt;text&gt;),&lt;text&gt;)。</font></p>
<p><font face="Courier New"><strong>$(firstword &lt;text&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：首单词函数——firstword。<br>&nbsp;&nbsp;&nbsp; 功能：取字符串&lt;text&gt;中的第一个单词。<br>&nbsp;&nbsp;&nbsp; 返回：返回字符串&lt;text&gt;的第一个单词。<br>&nbsp;&nbsp;&nbsp; 示例：$(firstword foo bar)返回值是&#8220;foo&#8221;。<br>&nbsp;&nbsp;&nbsp; 备注：这个函数可以用word函数来实现：$(word 1,&lt;text&gt;)。</font></p>
<p><font face="Courier New">以上，是所有的字符串操作函数，如果搭配混合使用，可以完成比较复杂的功能。这里，举一个现实中应用的例子。我们知道，make使用&#8220;VPATH&#8221;变量来指定&#8220;依赖文件&#8221;的搜索路径。于是，我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数CFLAGS，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 如果我们的&#8220;$(VPATH)&#8221;值是&#8220;src:../headers&#8221;，那么&#8220;$(patsubst %,-I%,$(subst :, ,$(VPATH)))&#8221;将返回&#8220;-Isrc -I../headers&#8221;，这正是cc或gcc搜索头文件路径的参数。</font></p>
<p><br><font face="Courier New"><strong>三、文件名操作函数</strong></font></p>
<p><font face="Courier New">下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。</font></p>
<p><font face="Courier New"><strong>$(dir &lt;names...&gt;)</strong> </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：取目录函数——dir。<br>&nbsp;&nbsp;&nbsp; 功能：从文件名序列&lt;names&gt;中取出目录部分。目录部分是指最后一个反斜杠（&#8220;/&#8221;）之前的部分。如果没有反斜杠，那么返回&#8220;./&#8221;。<br>&nbsp;&nbsp;&nbsp; 返回：返回文件名序列&lt;names&gt;的目录部分。<br>&nbsp;&nbsp;&nbsp; 示例： $(dir src/foo.c hacks)返回值是&#8220;src/ ./&#8221;。</font></p>
<p><font face="Courier New"><strong>$(notdir &lt;names...&gt;)</strong> </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：取文件函数——notdir。<br>&nbsp;&nbsp;&nbsp; 功能：从文件名序列&lt;names&gt;中取出非目录部分。非目录部分是指最后一个反斜杠（&#8220;/&#8221;）之后的部分。<br>&nbsp;&nbsp;&nbsp; 返回：返回文件名序列&lt;names&gt;的非目录部分。<br>&nbsp;&nbsp;&nbsp; 示例： $(notdir src/foo.c hacks)返回值是&#8220;foo.c hacks&#8221;。<br>&nbsp;<br><strong>$(suffix &lt;names...&gt;)</strong> <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 名称：取后缀函数——suffix。<br>&nbsp;&nbsp;&nbsp; 功能：从文件名序列&lt;names&gt;中取出各个文件名的后缀。<br>&nbsp;&nbsp;&nbsp; 返回：返回文件名序列&lt;names&gt;的后缀序列，如果文件没有后缀，则返回空字串。<br>&nbsp;&nbsp;&nbsp; 示例：$(suffix src/foo.c src-1.0/bar.c hacks)返回值是&#8220;.c .c&#8221;。</font></p>
<p><font face="Courier New"><strong>$(basename &lt;names...&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：取前缀函数——basename。<br>&nbsp;&nbsp;&nbsp; 功能：从文件名序列&lt;names&gt;中取出各个文件名的前缀部分。<br>&nbsp;&nbsp;&nbsp; 返回：返回文件名序列&lt;names&gt;的前缀序列，如果文件没有前缀，则返回空字串。<br>&nbsp;&nbsp;&nbsp; 示例：$(basename src/foo.c src-1.0/bar.c hacks)返回值是&#8220;src/foo src-1.0/bar hacks&#8221;。</font></p>
<p><font face="Courier New"><strong>$(addsuffix &lt;suffix&gt;,&lt;names...&gt;)</strong> </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：加后缀函数——addsuffix。<br>&nbsp;&nbsp;&nbsp; 功能：把后缀&lt;suffix&gt;加到&lt;names&gt;中的每个单词后面。<br>&nbsp;&nbsp;&nbsp; 返回：返回加过后缀的文件名序列。<br>&nbsp;&nbsp;&nbsp; 示例：$(addsuffix .c,foo bar)返回值是&#8220;foo.c bar.c&#8221;。</font></p>
<p><font face="Courier New"><strong>$(addprefix &lt;prefix&gt;,&lt;names...&gt;)</strong> </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：加前缀函数——addprefix。<br>&nbsp;&nbsp;&nbsp; 功能：把前缀&lt;prefix&gt;加到&lt;names&gt;中的每个单词后面。<br>&nbsp;&nbsp;&nbsp; 返回：返回加过前缀的文件名序列。<br>&nbsp;&nbsp;&nbsp; 示例：$(addprefix src/,foo bar)返回值是&#8220;src/foo src/bar&#8221;。</font></p>
<p><font face="Courier New"><strong>$(join &lt;list1&gt;,&lt;list2&gt;)</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 名称：连接函数——join。<br>&nbsp;&nbsp;&nbsp;
功能：把&lt;list2&gt;中的单词对应地加到&lt;list1&gt;的单词后面。如果&lt;list1&gt;的单词个数要
比&lt;list2&gt;的多，那么，&lt;list1&gt;中的多出来的单词将保持原样。如果&lt;list2&gt;的单词个数要
比&lt;list1&gt;多，那么，&lt;list2&gt;多出来的单词将被复制到&lt;list2&gt;中。<br>&nbsp;&nbsp;&nbsp; 返回：返回连接过后的字符串。<br>&nbsp;&nbsp;&nbsp; 示例：$(join aaa bbb , 111 222 333)返回值是&#8220;aaa111 bbb222 333&#8221;。</font></p><img src ="http://www.cppblog.com/yehongly/aggbug/62200.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-09-18 17:44 <a href="http://www.cppblog.com/yehongly/archive/2008/09/18/62200.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>写makefile——二</title><link>http://www.cppblog.com/yehongly/archive/2008/09/18/62199.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Thu, 18 Sep 2008 09:41:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/09/18/62199.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/62199.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/09/18/62199.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/62199.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/62199.html</trackback:ping><description><![CDATA[<p><font face="Courier New">规则包含两个部分，一个是依赖关系，一个是生成目标的方法。</font></p>
<p><font face="Courier New">在Makefile中，规则的顺序是很重要的，因为，Makefile中只应该有一个最终目
标，其它的目标都是被这个目标所连带出来的，所以一定要让make知道你的最终目标是什么。一般来说，定义在Makefile中的目标可能会有很多，但是
第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个，那么，第一个目标会成为最终的目标。make所完成的也就是这个目标。</font></p>
<p><font face="Courier New">好了，还是让我们来看一看如何书写规则。</font></p>
<p><br><font face="Courier New"><strong>一、规则举例</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo.o : foo.c defs.h&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # foo模块<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c -g foo.c</font></p>
<p><font face="Courier New">看到这个例子，各位应该不是很陌生了，前面也已说过，foo.o是我们的目标，foo.c和defs.h是目标所依赖的源文件，而只有一个命令&#8220;cc -c -g foo.c&#8221;（以Tab键开头）。这个规则告诉我们两件事：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 1、文件的依赖关系，foo.o依赖于foo.c和defs.h的文件，如果foo.c和defs.h的文件日期要比foo.o文件日期要新，或是foo.o不存在，那么依赖关系发生。<br>&nbsp;&nbsp;&nbsp; 2、如果生成（或更新）foo.o文件。也就是那个cc命令，其说明了，如何生成foo.o这个文件。（当然foo.c文件include了defs.h文件）</font></p>
<p><br><font face="Courier New"><strong>二、规则的语法</strong></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; targets : prerequisites<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; command<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 或是这样： </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; targets : prerequisites ; command<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; command<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...</font></p>
<p><font face="Courier New">targets是文件名，以空格分开，可以使用通配符。一般来说，我们的目标基本上是一个文件，但也有可能是多个文件。</font></p>
<p><font face="Courier New">command是命令行，如果其不与&#8220;target:prerequisites&#8221;在一行，那么，必须以[Tab键]开头，如果和prerequisites在一行，那么可以用分号做为分隔。（见上）</font></p>
<p><font face="Courier New">prerequisites也就是目标所依赖的文件（或依赖目标）。如果其中的某个文件要比目标文件要新，那么，目标就被认为是&#8220;过时的&#8221;，被认为是需要重生成的。这个在前面已经讲过了。</font></p>
<p><font face="Courier New">如果命令太长，你可以使用反斜框（&#8216;\&#8217;）作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事，文件的依赖关系和如何成成目标文件。</font></p>
<p><font face="Courier New">一般来说，make会以UNIX的标准Shell，也就是/bin/sh来执行命令。</font></p>
<p><br><font face="Courier New"><strong>三、在规则中使用通配符</strong></font></p>
<p><font face="Courier New">如果我们想定义一系列比较类似的文件，我们很自然地就想起使用通配符。make支持三各通配符：&#8220;*&#8221;，&#8220;?&#8221;和&#8220;[...]&#8221;。这是和Unix的B-Shell是相同的。</font></p>
<p><font face="Courier New">波浪号（&#8220;~&#8221;）字符在文件名中也有比较特殊的用途。如果是&#8220;~/test&#8221;，这就表示当前用
户的$HOME目录下的test目录。而&#8220;~hchen/test&#8221;则表示用户hchen的宿主目录下的test目录。（这些都是Unix下的小知识
了，make也支持）而在Windows或是MS-DOS下，用户没有宿主目录，那么波浪号所指的目录则根据环境变量&#8220;HOME&#8221;而定。</font></p>
<p><font face="Courier New">通配符代替了你一系列的文件，如&#8220;*.c&#8221;表示所以后缀为c的文件。一个需要我们注意的是，如果我们的文件名中有通配符，如：&#8220;*&#8221;，那么可以用转义字符&#8220;\&#8221;，如&#8220;\*&#8221;来表示真实的&#8220;*&#8221;字符，而不是任意长度的字符串。</font></p>
<p><font face="Courier New">好吧，还是先来看几个例子吧：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; clean:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm -f *.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 上面这个例子我不不多说了，这是操作系统Shell所支持的通配符。这是在命令中的通配符。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; print: *.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpr -p $?<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; touch print</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 上面这个例子说明了通配符也可以在我们的规则中，目标print依赖于所有的[.c]文件。其中的&#8220;$?&#8221;是一个自动化变量，我会在后面给你讲述。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; objects = *.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;
上面这个例子，表示了，通符同样可以用在变量中。并不是说[*.o]会展开，不！objects的值就是&#8220;*.o&#8221;。Makefile中的变量其实就是
C/C++中的宏。如果你要让通配符在变量中展开，也就是让objects的值是所有[.o]的文件名的集合，那么，你可以这样：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; objects := $(wildcard *.o)</font></p>
<p><font face="Courier New">这种用法由关键字&#8220;wildcard&#8221;指出，关于Makefile的关键字，我们将在后面讨论。</font></p>
<p><br><font face="Courier New"><strong>四、文件搜寻</strong></font></p>
<p><font face="Courier New">在一些大的工程中，有大量的源文件，我们通常的做法是把这许多的源文件分类，并存放在不同的目录中。所以，当make需要去找寻文件的依赖关系时，你可以在文件前加上路径，但最好的方法是把一个路径告诉make，让make在自动去找。</font></p>
<p><font face="Courier New">Makefile文件中的特殊变量&#8220;VPATH&#8221;就是完成这个功能的，如果没有指明这个变量，make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量，那么，make就会在当当前目录找不到的情况下，到所指定的目录中去找寻文件了。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; VPATH = src:../headers</font></p>
<p><font face="Courier New">上面的的定义指定两个目录，&#8220;src&#8221;和&#8220;../headers&#8221;，make会按照这个顺序进行搜索。目录由&#8220;冒号&#8221;分隔。（当然，当前目录永远是最高优先搜索的地方）</font></p>
<p><font face="Courier New">另一个设置文件搜索路径的方法是使用make的&#8220;vpath&#8221;关键字（注意，它是全小写的），
这不是变量，这是一个make的关键字，这和上面提到的那个VPATH变量很类似，但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很
灵活的功能。它的使用方法有三种：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 1、vpath &lt;pattern&gt; &lt;directories&gt;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 为符合模式&lt;pattern&gt;的文件指定搜索目录&lt;directories&gt;。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 2、vpath &lt;pattern&gt;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 清除符合模式&lt;pattern&gt;的文件的搜索目录。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 3、vpath</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 清除所有已被设置好了的文件搜索目录。</font></p>
<p><font face="Courier New">vapth使用方法中的&lt;pattern&gt;需要包含&#8220;%&#8221;字符。&#8220;%&#8221;的意思是匹
配零或若干字符，例如，&#8220;%.h&#8221;表示所有以&#8220;.h&#8221;结尾的文件。&lt;pattern&gt;指定了要搜索的文件集，
而&lt;directories&gt;则指定了&lt;pattern&gt;的文件集的搜索的目录。例如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; vpath %.h ../headers</font></p>
<p><font face="Courier New">该语句表示，要求make在&#8220;../headers&#8221;目录下搜索所有以&#8220;.h&#8221;结尾的文件。（如果某文件在当前目录没有找到的话）</font></p>
<p><font face="Courier New">我们可以连续地使用vpath语句，以指定不同搜索策略。如果连续的vpath语句中出现了相同的&lt;pattern&gt;，或是被重复了的&lt;pattern&gt;，那么，make会按照vpath语句的先后顺序来执行搜索。如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; vpath %.c foo<br>&nbsp;&nbsp;&nbsp; vpath %&nbsp;&nbsp; blish<br>&nbsp;&nbsp;&nbsp; vpath %.c bar</font></p>
<p><font face="Courier New">其表示&#8220;.c&#8221;结尾的文件，先在&#8220;foo&#8221;目录，然后是&#8220;blish&#8221;，最后是&#8220;bar&#8221;目录。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; vpath %.c foo:bar<br>&nbsp;&nbsp;&nbsp; vpath %&nbsp;&nbsp; blish</font></p>
<p><font face="Courier New">而上面的语句则表示&#8220;.c&#8221;结尾的文件，先在&#8220;foo&#8221;目录，然后是&#8220;bar&#8221;目录，最后才是&#8220;blish&#8221;目录。</font></p>
<p><br><font face="Courier New"><strong>五、伪目标</strong></font></p>
<p><font face="Courier New">最早先的一个例子中，我们提到过一个&#8220;clean&#8221;的目标，这是一个&#8220;伪目标&#8221;，</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; clean:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm *.o temp</font></p>
<p><font face="Courier New">正像我们前面例子中的&#8220;clean&#8221;一样，即然我们生成了许多文件编译文件，我们也应该提供一个清除它们的&#8220;目标&#8221;以备完整地重编译而用。 （以&#8220;make clean&#8221;来使用该目标）</font></p>
<p><font face="Courier New">因为，我们并不生成&#8220;clean&#8221;这个文件。&#8220;伪目标&#8221;并不是一个文件，只是一个标签，由于&#8220;
伪目标&#8221;不是文件，所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个&#8220;目标&#8221;才能让其生效。当然，&#8220;伪目标&#8221;的取名不能
和文件名重名，不然其就失去了&#8220;伪目标&#8221;的意义了。</font></p>
<p><font face="Courier New">当然，为了避免和文件重名的这种情况，我们可以使用一个特殊的标记&#8220;.PHONY&#8221;来显示地指明一个目标是&#8220;伪目标&#8221;，向make说明，不管是否有这个文件，这个目标就是&#8220;伪目标&#8221;。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; .PHONY : clean</font></p>
<p><font face="Courier New">只要有这个声明，不管是否有&#8220;clean&#8221;文件，要运行&#8220;clean&#8221;这个目标，只有&#8220;make clean&#8221;这样。于是整个过程可以这样写：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp; .PHONY: clean<br>&nbsp;&nbsp;&nbsp; clean:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm *.o temp</font></p>
<p><font face="Courier New">伪目标一般没有依赖的文件。但是，我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为&#8220;
默认目标&#8221;，只要将其放在第一个。一个示例就是，如果你的Makefile需要一口气生成若干个可执行文件，但你只想简单地敲一个make完事，并且，所
有的目标文件都写在一个Makefile中，那么你可以使用&#8220;伪目标&#8221;这个特性：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; all : prog1 prog2 prog3<br>&nbsp;&nbsp;&nbsp; .PHONY : all</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; prog1 : prog1.o utils.o<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o prog1 prog1.o utils.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; prog2 : prog2.o<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o prog2 prog2.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; prog3 : prog3.o sort.o utils.o<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o prog3 prog3.o sort.o utils.o</font></p>
<p><font face="Courier New">我们知道，Makefile中的第一个目标会被作为其默认目标。我们声明了一个&#8220;all&#8221;的伪
目标，其依赖于其它三个目标。由于伪目标的特性是，总是被执行的，所以其依赖的那三个目标就总是不如&#8220;all&#8221;这个目标新。所以，其它三个目标的规则总是
会被决议。也就达到了我们一口气生成多个目标的目的。&#8220;.PHONY : all&#8221;声明了&#8220;all&#8221;这个目标为&#8220;伪目标&#8221;。</font></p>
<p><font face="Courier New">随便提一句，从上面的例子我们可以看出，目标也可以成为依赖。所以，伪目标同样也可成为依赖。看下面的例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; .PHONY: cleanall cleanobj cleandiff</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; cleanall : cleanobj cleandiff<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm program</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; cleanobj :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm *.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; cleandiff :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm *.diff</font></p>
<p><font face="Courier New">&#8220;make
clean&#8221;将清除所有要被清除的文件。&#8220;cleanobj&#8221;和&#8220;cleandiff&#8221;这两个伪目标有点像&#8220;子程序&#8221;的意思。我们可以输入&#8220;make
cleanall&#8221;和&#8220;make cleanobj&#8221;和&#8220;make cleandiff&#8221;命令来达到清除不同种类文件的目的。</font></p>
<p><font face="Courier New"><strong>六、多目标</strong></font></p>
<p><font face="Courier New">Makefile的规则中的目标可以不止一个，其支持多目标，有可能我们的多个目标同时依赖于
一个文件，并且其生成的命令大体类似。于是我们就能把其合并起来。当然，多个目标的生成规则的执行命令是同一个，这可能会可我们带来麻烦，不过好在我们的
可以使用一个自动化变量&#8220;$@&#8221;（关于自动化变量，将在后面讲述），这个变量表示着目前规则中所有的目标的集合，这样说可能很抽象，还是看一个例子吧。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; bigoutput littleoutput : text.g<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; generate text.g -$(subst output,,$@) &gt; $@</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 上述规则等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; bigoutput : text.g<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; generate text.g -big &gt; bigoutput<br>&nbsp;&nbsp;&nbsp; littleoutput : text.g<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; generate text.g -little &gt; littleoutput</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 其中，-$(subst
output,,$@)中的&#8220;$&#8221;表示执行一个Makefile的函数，函数名为subst，后面的为参数。关于函数，将在后面讲述。这里的这个函数是截
取字符串的意思，&#8220;$@&#8221;表示目标的集合，就像一个数组，&#8220;$@&#8221;依次取出目标，并执于命令。</font></p>
<p><br><font face="Courier New"><strong>七、静态模式</strong></font></p>
<p><font face="Courier New">静态模式可以更加容易地定义多目标的规则，可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &lt;targets ...&gt;: &lt;target-pattern&gt;: &lt;prereq-patterns ...&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;commands&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...</font></p>
<p><br><font face="Courier New">&nbsp;&nbsp;&nbsp; targets定义了一系列的目标文件，可以有通配符。是目标的一个集合。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; target-parrtern是指明了targets的模式，也就是的目标集模式。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; prereq-parrterns是目标的依赖模式，它对target-parrtern形成的模式再进行一次依赖目标的定义。</font></p>
<p><font face="Courier New">这样描述这三个东西，可能还是没有说清楚，还是举个例子来说明一下吧。如果我们
的&lt;target-parrtern&gt;定义成&#8220;%.o&#8221;，意思是我们的&lt;target&gt;集合中都是以&#8220;.o&#8221;结尾的，而如果我们
的&lt;prereq-parrterns&gt;定义成&#8220;%.c&#8221;，意思是对&lt;target-parrtern&gt;所形成的目标集进行二次
定义，其计算方法是，取&lt;target-parrtern&gt;模式中的&#8220;%&#8221;（也就是去掉了[.o]这个结尾），并为其加上[.c]这个结尾，
形成的新集合。</font></p>
<p><font face="Courier New">所以，我们的&#8220;目标模式&#8221;或是&#8220;依赖模式&#8221;中都应该有&#8220;%&#8221;这个字符，如果你的文件名中有&#8220;%&#8221;那么你可以使用反斜杠&#8220;\&#8221;进行转义，来标明真实的&#8220;%&#8221;字符。</font></p>
<p><font face="Courier New">看一个例子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; objects = foo.o bar.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; all: $(objects)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; $(objects): %.o: %.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -c $(CFLAGS) $&lt; -o $@</font></p>
<p><font face="Courier New"><br>上面的例子中，指明了我们的目标从$object中获取，&#8220;%.o&#8221;表明要所有以
&#8220;.o&#8221;结尾的目标，也就是&#8220;foo.o
bar.o&#8221;，也就是变量$object集合的模式，而依赖模式&#8220;%.c&#8221;则取模式&#8220;%.o&#8221;的&#8220;%&#8221;，也就是&#8220;foo
bar&#8221;，并为其加下&#8220;.c&#8221;的后缀，于是，我们的依赖目标就是&#8220;foo.c
bar.c&#8221;。而命令中的&#8220;$&lt;&#8221;和&#8220;$@&#8221;则是自动化变量，&#8220;$&lt;&#8221;表示所有的依赖目标集（也就是&#8220;foo.c
bar.c&#8221;），&#8220;$@&#8221;表示目标集（也就是&#8220;foo.o bar.o&#8221;）。于是，上面的规则展开后等价于下面的规则：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo.o : foo.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -c $(CFLAGS) foo.c -o foo.o<br>&nbsp;&nbsp;&nbsp; bar.o : bar.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -c $(CFLAGS) bar.c -o bar.o</font></p>
<p><font face="Courier New">试想，如果我们的&#8220;%.o&#8221;有几百个，那种我们只要用这种很简单的&#8220;静态模式规则&#8221;就可以写完一堆规则，实在是太有效率了。&#8220;静态模式规则&#8221;的用法很灵活，如果用得好，那会一个很强大的功能。再看一个例子：</font></p>
<p><br><font face="Courier New">&nbsp;&nbsp;&nbsp; files = foo.elc bar.o lose.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; $(filter %.o,$(files)): %.o: %.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -c $(CFLAGS) $&lt; -o $@<br>&nbsp;&nbsp;&nbsp; $(filter %.elc,$(files)): %.elc: %.el<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; emacs -f batch-byte-compile $&lt;</font></p>
<p><font face="Courier New"><br>$(filter %.o,$(files))表示调用Makefile的filter函数，过滤&#8220;$filter&#8221;集，只要其中模式为&#8220;%.o&#8221;的内容。其的它内容，我就不用多说了吧。这个例字展示了Makefile中更大的弹性。</font></p>
<p><br><font face="Courier New"><strong>八、自动生成依赖性</strong></font></p>
<p><font face="Courier New">在Makefile中，我们的依赖关系可能会需要包含一系列的头文件，比如，如果我们的main.c中有一句&#8220;#include "defs.h"&#8221;，那么我们的依赖关系应该是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; main.o : main.c defs.h</font></p>
<p><font face="Courier New">但是，如果是一个比较大型的工程，你必需清楚哪些C文件包含了哪些头文件，并且，你在加入或删
除头文件时，也需要小心地修改Makefile，这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情，我们可以使用C/C++编译的一个功
能。大多数的C/C++编译器都支持一个&#8220;-M&#8221;的选项，即自动找寻源文件中包含的头文件，并生成一个依赖关系。例如，如果我们执行下面的命令：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; cc -M main.c</font></p>
<p><font face="Courier New">其输出是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; main.o : main.c defs.h</font></p>
<p><font face="Courier New">于是由编译器自动生成的依赖关系，这样一来，你就不必再手动书写若干文件的依赖关系，而由编译器自动生成了。需要提醒一句的是，如果你使用GNU的C/C++编译器，你得用&#8220;-MM&#8221;参数，不然，&#8220;-M&#8221;参数会把一些标准库的头文件也包含进来。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; gcc -M main.c的输出是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/include/bits/sched.h /usr/include/libio.h \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/include/_G_config.h /usr/include/wchar.h \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/include/bits/wchar.h /usr/include/gconv.h \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/include/bits/stdio_lim.h</font></p>
<p><br><font face="Courier New">&nbsp;&nbsp;&nbsp; gcc -MM main.c的输出则是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; main.o: main.c defs.h</font></p>
<p><font face="Courier New">那么，编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来，我们的
Makefile也要根据这些源文件重新生成，让Makefile自已依赖于源文件？这个功能并不现实，不过我们可以有其它手段来迂回地实现这一功能。
GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中，为每一个&#8220;name.c&#8221;的文件都生成一个&#8220;name.d&#8221;的
Makefile文件，[.d]文件中就存放对应[.c]文件的依赖关系。</font></p>
<p><font face="Courier New">于是，我们可以写出[.c]文件和[.d]文件的依赖关系，并让make自动更新或自成[.d]文件，并把其包含在我们的主Makefile中，这样，我们就可以自动化地生成每个文件的依赖关系了。</font></p>
<p><font face="Courier New">这里，我们给出了一个模式规则来产生[.d]文件：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; %.d: %.c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @set -e; rm -f $@; \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -M $(CPPFLAGS) $&lt; &gt; </font><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#36;&#64;&#46;&#36;&#36;&#36;&#36;"><font face="Courier New">$@.$$$$</font></a><font face="Courier New">; \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' &lt; </font><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#36;&#64;&#46;&#36;&#36;&#36;&#36;"><font face="Courier New">$@.$$$$</font></a><font face="Courier New"> &gt; $@; \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm -f </font><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#36;&#64;&#46;&#36;&#36;&#36;&#36;"><font face="Courier New">$@.$$$$</font></a></p>
<p><font face="Courier New"><br>这个规则的意思是，所有的[.d]文件依赖于[.c]文件，&#8220;rm -f
$@&#8221;的意思是删除所有的目标，也就是[.d]文件，第二行的意思是，为每个依赖文件&#8220;$&lt;&#8221;，也就是[.c]文件生成依赖文件，&#8220;$@&#8221;表示模式
&#8220;%.d&#8221;文件，如果有一个C文件是name.c，那么&#8220;%&#8221;就是&#8220;name&#8221;，&#8220;$$$$&#8221;意为一个随机编号，第二行生成的文件有可能是
&#8220;name.d.12345&#8221;，第三行使用sed命令做了一个替换，关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。</font></p>
<p><font face="Courier New">总而言之，这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖，即把依赖关系：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; main.o : main.c defs.h</font></p>
<p><font face="Courier New">转成：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; main.o main.d : main.c defs.h</font></p>
<p><font face="Courier New">于是，我们的[.d]文件也会自动更新了，并会自动生成了，当然，你还可以在这个[.d]文件
中加入的不只是依赖关系，包括生成的命令也可一并加入，让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作，接下来，我们就要把这些自动生成
的规则放进我们的主Makefile中。我们可以使用Makefile的&#8220;include&#8221;命令，来引入别的Makefile文件（前面讲过），例如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; sources = foo.c bar.c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; include $(sources:.c=.d)</font></p>
<p><font face="Courier New">上述语句中的&#8220;$(sources:.c=.d)&#8221;中的&#8220;.c=.d&#8221;的意思是做一个替换，把
变量$(sources)所有[.c]的字串都替换成[.d]，关于这个&#8220;替换&#8221;的内容，在后面我会有更为详细的讲述。当然，你得注意次序，因为
include是按次来载入文件，最先载入的[.d]文件中的目标会成为默认目标。</font></p>
<p><font face="Courier New">每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命
令，每条命令的开头必须以[Tab]键开头，除非，命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略，但是如果该空格或空行是
以Tab键开头的，那么make会认为其是一个空命令。</font></p>
<p><font face="Courier New">我们在UNIX下可能会使用不同的Shell，但是make的命令默认是被&#8220;/bin/sh&#8221;——UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中，&#8220;#&#8221;是注释符，很像C/C++中的&#8220;//&#8221;，其后的本行字符都被注释。</font></p>
<p><font face="Courier New"><strong>一、显示命令</strong></font></p>
<p><font face="Courier New">通常，make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用&#8220;@&#8221;字符在命令行前，那么，这个命令将不被make显示出来，最具代表性的例子是，我们用这个功能来像屏幕显示一些信息。如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; @echo 正在编译XXX模块......</font></p>
<p><font face="Courier New">当make执行时，会输出&#8220;正在编译XXX模块......&#8221;字串，但不会输出命令，如果没有&#8220;@&#8221;，那么，make将输出：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; echo 正在编译XXX模块......<br>&nbsp;&nbsp;&nbsp; 正在编译XXX模块......</font></p>
<p><font face="Courier New">如果make执行时，带入make参数&#8220;-n&#8221;或&#8220;--just-print&#8221;，那么其只是显示命令，但不会执行命令，这个功能很有利于我们调试我们的Makefile，看看我们书写的命令是执行起来是什么样子的或是什么顺序的。</font></p>
<p><font face="Courier New">而make参数&#8220;-s&#8221;或&#8220;--slient&#8221;则是全面禁止命令的显示。</font></p>
<p>&nbsp;</p>
<p><font face="Courier New"><strong>二、命令执行</strong></font></p>
<p><font face="Courier New">当依赖目标新于目标时，也就是当规则的目标需要被更新时，make会一条一条的执行其后的命
令。需要注意的是，如果你要让上一条命令的结果应用在下一条命令时，你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令，你希望第二条命令得在
cd之后的基础上运行，那么你就不能把这两条命令写在两行上，而应该把这两条命令写在一行上，用分号分隔。如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 示例一：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exec:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cd /home/hchen<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pwd</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 示例二：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exec:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cd /home/hchen; pwd</font></p>
<p><font face="Courier New">当我们执行&#8220;make exec&#8221;时，第一个例子中的cd没有作用，pwd会打印出当前的Makefile目录，而第二个例子中，cd就起作用了，pwd会打印出&#8220;/home/hchen&#8221;。</font></p>
<p><font face="Courier New">make一般是使用环境变量SHELL中所定义的系统Shell来执行命令，默认情况下使用
UNIX的标准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊，因为MS-DOS下没有SHELL环境变量，当然你也可以指定。如
果你指定了UNIX风格的目录形式，首先，make会在SHELL所指定的路径中找寻命令解释器，如果找不到，其会在当前盘符中的当前目录中寻找，如果再
找不到，其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中，如果你定义的命令解释器没有找到，其会给你的命令解释器加上诸如
&#8220;.exe&#8221;、&#8220;.com&#8221;、&#8220;.bat&#8221;、&#8220;.sh&#8221;等后缀。</font></p>
<p><br><br><font face="Courier New"><strong>三、命令出错</strong></font></p>
<p><font face="Courier New">每当命令运行完后，make会检测每个命令的返回码，如果命令返回成功，那么make会执行下
一条命令，当规则中所有的命令成功返回后，这个规则就算是成功完成了。如果一个规则中的某个命令出错了（命令退出码非零），那么make就会终止执行当前
规则，这将有可能终止所有规则的执行。</font></p>
<p><font face="Courier New">有些时候，命令的出错并不表示就是错误的。例如mkdir命令，我们一定需要建立一个目录，如
果目录不存在，那么mkdir就成功执行，万事大吉，如果目录存在，那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录，于是我们
就不希望mkdir出错而终止规则的运行。</font></p>
<p><font face="Courier New">为了做到这一点，忽略命令的出错，我们可以在Makefile的命令行前加一个减号&#8220;-&#8221;（在Tab键之后），标记为不管命令出不出错都认为是成功的。如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp; clean:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -rm -f *.o</font></p>
<p><font face="Courier New">还有一个全局的办法是，给make加上&#8220;-i&#8221;或是&#8220;--ignore-errors&#8221;参数，
那么，Makefile中所有命令都会忽略错误。而如果一个规则是以&#8220;.IGNORE&#8221;作为目标的，那么这个规则中的所有命令将会忽略错误。这些是不同级
别的防止命令出错的方法，你可以根据你的不同喜欢设置。</font></p>
<p><font face="Courier New">还有一个要提一下的make的参数的是&#8220;-k&#8221;或是&#8220;--keep-going&#8221;，这个参数的意思是，如果某规则中的命令出错了，那么就终目该规则的执行，但继续执行其它规则。</font></p>
<p><br><br><font face="Courier New"><strong>四、嵌套执行make</strong></font></p>
<p><font face="Courier New">在一些大的工程中，我们会把我们不同模块或是不同功能的源文件放在不同的目录中，我们可以在每
个目录中都书写一个该目录的Makefile，这有利于让我们的Makefile变得更加地简洁，而不至于把所有的东西全部写在一个Makefile中，
这样会很难维护我们的Makefile，这个技术对于我们模块编译和分段编译有着非常大的好处。</font></p>
<p><font face="Courier New">例如，我们有一个子目录叫subdir，这个目录下有个Makefile文件，来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; subsystem:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cd subdir &amp;&amp; $(MAKE)</font></p>
<p><font face="Courier New">其等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; subsystem:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(MAKE) -C subdir</font></p>
<p><font face="Courier New">定义$(MAKE)宏变量的意思是，也许我们的make需要一些参数，所以定义成一个变量比较利于维护。这两个例子的意思都是先进入&#8220;subdir&#8221;目录，然后执行make命令。</font></p>
<p><font face="Courier New">我们把这个Makefile叫做&#8220;总控Makefile&#8221;，总控Makefile的变量可以传递到下级的Makefile中（如果你显示的声明），但是不会覆盖下层的Makefile中所定义的变量，除非指定了&#8220;-e&#8221;参数。</font></p>
<p><font face="Courier New">如果你要传递变量到下级Makefile中，那么你可以使用这样的声明：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; export &lt;variable ...&gt;</font></p>
<p><font face="Courier New">如果你不想让某些变量传递到下级Makefile中，那么你可以这样声明： </font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; unexport &lt;variable ...&gt;</font></p>
<p><font face="Courier New">如：<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 示例一：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; export variable = value</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; variable = value<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; export variable</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; export variable := value</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; variable := value<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; export variable</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 示例二：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; export variable += value</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; variable += value<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; export variable</font></p>
<p><font face="Courier New">如果你要传递所有的变量，那么，只要一个export就行了。后面什么也不用跟，表示传递所有的变量。</font></p>
<p><font face="Courier New">需要注意的是，有两个变量，一个是SHELL，一个是MAKEFLAGS，这两个变量不管你是
否export，其总是要传递到下层Makefile中，特别是MAKEFILES变量，其中包含了make的参数信息，如果我们执行&#8220;总控
Makefile&#8221;时有make参数或是在上层Makefile中定义了这个变量，那么MAKEFILES变量将会是这些参数，并会传递到下层
Makefile中，这是一个系统级的环境变量。</font></p>
<p><font face="Courier New">但是make命令中的有几个参数并不往下传递，它们是&#8220;-C&#8221;,&#8220;-f&#8221;,&#8220;-h&#8221;&#8220;-o&#8221;和&#8220;-W&#8221;（有关Makefile参数的细节将在后面说明），如果你不想往下层传递参数，那么，你可以这样来：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; subsystem:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cd subdir &amp;&amp; $(MAKE) MAKEFLAGS=</font></p>
<p><font face="Courier New">如果你定义了环境变量MAKEFLAGS，那么你得确信其中的选项是大家都会用到的，如果其中有&#8220;-t&#8221;,&#8220;-n&#8221;,和&#8220;-q&#8221;参数，那么将会有让你意想不到的结果，或许会让你异常地恐慌。</font></p>
<p><font face="Courier New">还有一个在&#8220;嵌套执行&#8221;中比较有用的参数，&#8220;-w&#8221;或是&#8220;--print-
directory&#8221;会在make的过程中输出一些信息，让你看到目前的工作目录。比如，如果我们的下级make目录是&#8220;/home/hchen/gnu
/make&#8221;，如果我们使用&#8220;make -w&#8221;来执行，那么当进入该目录时，我们会看到：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; make: Entering directory `/home/hchen/gnu/make'.</font></p>
<p><font face="Courier New">而在完成下层make后离开目录时，我们会看到：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; make: Leaving directory `/home/hchen/gnu/make'</font></p>
<p><font face="Courier New">当你使用&#8220;-C&#8221;参数来指定make下层Makefile时，&#8220;-w&#8221;会被自动打开的。如果参数中有&#8220;-s&#8221;（&#8220;--slient&#8221;）或是&#8220;--no-print-directory&#8221;，那么，&#8220;-w&#8221;总是失效的。</font></p>
<p><br><br><font face="Courier New"><strong>五、定义命令包</strong></font></p>
<p><font face="Courier New">如果Makefile中出现一些相同命令序列，那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以&#8220;define&#8221;开始，以&#8220;endef&#8221;结束，如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; define run-yacc<br>&nbsp;&nbsp;&nbsp; yacc $(firstword $^)<br>&nbsp;&nbsp;&nbsp; mv y.tab.c $@<br>&nbsp;&nbsp;&nbsp; endef</font></p>
<p><font face="Courier New">这里，&#8220;run-yacc&#8221;是这个命令包的名字，其不要和Makefile中的变量重名。在
&#8220;define&#8221;和&#8220;endef&#8221;中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序，因为Yacc程序总是生成&#8220;y.tab.c&#8221;的文
件，所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; foo.c : foo.y<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(run-yacc)</font></p>
<p><font face="Courier New">我们可以看见，要使用这个命令包，我们就好像使用变量一样。在这个命令包的使用中，命令包
&#8220;run-yacc&#8221;中的&#8220;$^&#8221;就是&#8220;foo.y&#8221;，&#8220;$@&#8221;就是&#8220;foo.c&#8221;（有关这种以&#8220;$&#8221;开头的特殊变量，我们会在后面介绍），make在执
行命令包时，命令包中的每个命令会被依次独立执行。</font></p><img src ="http://www.cppblog.com/yehongly/aggbug/62199.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-09-18 17:41 <a href="http://www.cppblog.com/yehongly/archive/2008/09/18/62199.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>跟我一起写 Makefile</title><link>http://www.cppblog.com/yehongly/archive/2008/09/18/62197.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Thu, 18 Sep 2008 09:39:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/09/18/62197.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/62197.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/09/18/62197.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/62197.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/62197.html</trackback:ping><description><![CDATA[http://dev.csdn.net/develop/article/20/20025.shtm<br><br><span id="ArticleContent1_ArticleContent1_lblContent">
<p align="left"><font size="4" face="Courier New"><strong>概述<br>——</strong></font></p>
<p><font face="Courier New">什么是makefile？或许很多Winodws的程序员都不知道这个东西，因为那些
Windows的IDE都为你做了这个工作，但我觉得要作一个好的和professional的程序员，makefile还是要懂。这就好像现在有这么多
的HTML的编辑器，但如果你想成为一个专业人士，你还是要了解HTML的标识的含义。特别在Unix下的软件编译，你就不能不自己写makefile
了，会不会写makefile，从一个侧面说明了一个人是否具备完成大型工程的能力。</font></p>
<p><font face="Courier New">因为，makefile关系到了整个工程的编译规则。一个工程中的源文件不计数，其按类型、功
能、模块分别放在若干个目录中，makefile定义了一系列的规则来指定，哪些文件需要先编译，哪些文件需要后编译，哪些文件需要重新编译，甚至于进行
更复杂的功能操作，因为makefile就像一个Shell脚本一样，其中也可以执行操作系统的命令。</font></p>
<p><font face="Courier New">makefile带来的好处就是——&#8220;自动化编译&#8221;，一旦写好，只需要一个make命令，整个
工程完全自动编译，极大的提高了软件开发的效率。make是一个命令工具，是一个解释makefile中指令的命令工具，一般来说，大多数的IDE都有这
个命令，比如：Delphi的make，Visual
C++的nmake，Linux下GNU的make。可见，makefile都成为了一种在工程方面的编译方法。</font></p>
<p><font face="Courier New">现在讲述如何写makefile的文章比较少，这是我想写这篇文章的原因。当然，不同产商的
make各不相同，也有不同的语法，但其本质都是在&#8220;文件依赖性&#8221;上做文章，这里，我仅对GNU的make进行讲述，我的环境是RedHat
Linux 8.0，make的版本是3.80。必竟，这个make是应用最为广泛的，也是用得最多的。而且其还是最遵循于IEEE
1003.2-1992 标准的（POSIX.2）。</font></p>
<p><font face="Courier New">在这篇文档中，将以C/C++的源码作为我们基础，所以必然涉及一些关于C/C++的编译的知识，相关于这方面的内容，还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。</font></p>
<p>&nbsp;</p>
<p><font face="Courier New"><strong>关于程序的编译和链接<br>——————————</strong></font></p>
<p><font face="Courier New">在此，我想多说关于程序编译的一些规范和方法，一般来说，无论是C、C++、还是pas，首先
要把源文件编译成中间代码文件，在Windows下也就是 .obj 文件，UNIX下是 .o 文件，即 Object
File，这个动作叫做编译（compile）。然后再把大量的Object File合成执行文件，这个动作叫作链接（link）。</font></p>
<p><font face="Courier New">编译时，编译器需要的是语法的正确，函数与变量的声明的正确。对于后者，通常是你需要告诉编译
器头文件的所在位置（头文件中应该只是声明，而定义应该放在C/C++文件中），只要所有的语法正确，编译器就可以编译出中间目标文件。一般来说，每个源
文件都应该对应于一个中间目标文件（O文件或是OBJ文件）。</font></p>
<p><font face="Courier New">链接时，主要是链接函数和全局变量，所以，我们可以使用这些中间目标文件（O文件或是OBJ文
件）来链接我们的应用程序。链接器并不管函数所在的源文件，只管函数的中间目标文件（Object
File），在大多数时候，由于源文件太多，编译生成的中间目标文件太多，而在链接时需要明显地指出中间目标文件名，这对于编译很不方便，所以，我们要给
中间目标文件打个包，在Windows下这种包叫&#8220;库文件&#8221;（Library File)，也就是 .lib 文件，在UNIX下，是Archive
File，也就是 .a 文件。</font></p>
<p><font face="Courier New">总结一下，源文件首先会生成中间目标文件，再由中间目标文件生成执行文件。在编译时，编译器只
检测程序语法，和函数、变量是否被声明。如果函数未被声明，编译器会给出一个警告，但可以生成Object
File。而在链接程序时，链接器会在所有的Object File中找寻函数的实现，如果找不到，那到就会报链接错误码（Linker
Error），在VC下，这种错误一般是：Link 2001错误，意思说是说，链接器未能找到函数的实现。你需要指定函数的Object File.</font></p>
<p><font face="Courier New">好，言归正传，GNU的make有许多的内容，闲言少叙，还是让我们开始吧。</font></p>
<p>&nbsp;</p>
<p><font face="Courier New"><strong>Makefile 介绍<br>———————</strong></font></p>
<p><font face="Courier New">make命令执行时，需要一个 Makefile 文件，以告诉make命令需要怎么样的去编译和链接程序。</font></p>
<p><font face="Courier New">首先，我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册，在这个示例中，我们的工程有8个C文件，和3个头文件，我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是：<br>&nbsp;&nbsp;&nbsp; 1）如果这个工程没有编译过，那么我们的所有C文件都要编译并被链接。<br>&nbsp;&nbsp;&nbsp; 2）如果这个工程的某几个C文件被修改，那么我们只编译被修改的C文件，并链接目标程序。<br>&nbsp;&nbsp;&nbsp; 3）如果这个工程的头文件被改变了，那么我们需要编译引用了这几个头文件的C文件，并链接目标程序。</font></p>
<p><font face="Courier New">只要我们的Makefile写得够好，所有的这一切，我们只用一个make命令就可以完成，make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译，从而自己编译所需要的文件和链接目标程序。</font></p>
<p><br><font face="Courier New"><strong>一、Makefile的规则</strong></font></p>
<p><font face="Courier New">在讲述这个Makefile之前，还是让我们先来粗略地看一看Makefile的规则。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; target ... : prerequisites ...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; command<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; target也就是一个目标文件，可以是Object File，也可以是执行文件。还可以是一个标签（Label），对于标签这种特性，在后续的&#8220;伪目标&#8221;章节中会有叙述。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; prerequisites就是，要生成那个target所需要的文件或是目标。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; command也就是make需要执行的命令。（任意的Shell命令）</font></p>
<p><font face="Courier New">这是一个文件的依赖关系，也就是说，target这一个或多个的目标文件依赖于
prerequisites中的文件，其生成规则定义在command中。说白一点就是说，prerequisites中如果有一个以上的文件比
target文件要新的话，command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。</font></p>
<p><font face="Courier New">说到底，Makefile的东西就是这样一点，好像我的这篇文档也该结束了。呵呵。还不尽然，这是Makefile的主线和核心，但要写好一个Makefile还不够，我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。：）</font></p>
<p><br><font face="Courier New"><strong>二、一个示例</strong></font></p>
<p><font face="Courier New">正如前面所说的，如果一个工程有3个头文件，和8个C文件，我们为了完成前面所述的那三个规则，我们的Makefile应该是下面的这个样子的。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; edit : main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o edit main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; main.o : main.c defs.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c main.c<br>&nbsp;&nbsp;&nbsp; kbd.o : kbd.c defs.h command.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c kbd.c<br>&nbsp;&nbsp;&nbsp; command.o : command.c defs.h command.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c command.c<br>&nbsp;&nbsp;&nbsp; display.o : display.c defs.h buffer.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c display.c<br>&nbsp;&nbsp;&nbsp; insert.o : insert.c defs.h buffer.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c insert.c<br>&nbsp;&nbsp;&nbsp; search.o : search.c defs.h buffer.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c search.c<br>&nbsp;&nbsp;&nbsp; files.o : files.c defs.h buffer.h command.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c files.c<br>&nbsp;&nbsp;&nbsp; utils.o : utils.c defs.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c utils.c<br>&nbsp;&nbsp;&nbsp; clean :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm edit main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o</font></p>
<p><font face="Courier New">反斜杠（\）是换行符的意思。这样比较便于Makefile的易读。我们可以把这个内容保存在
文件为&#8220;Makefile&#8221;或&#8220;makefile&#8221;的文件中，然后在该目录下直接输入命令&#8220;make&#8221;就可以生成执行文件edit。如果要删除执行文件和
所有的中间目标文件，那么，只要简单地执行一下&#8220;make clean&#8221;就可以了。</font></p>
<p><font face="Courier New">在这个makefile中，目标文件（target）包含：执行文件edit和中间目标文件
（*.o），依赖文件（prerequisites）就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件，而这些 .o
文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的，换言之，目标文件是哪些文件更新的。</font></p>
<p><font face="Courier New">在定义好依赖关系后，后续的那一行定义了如何生成目标文件的操作系统命令，一定要以一个Tab
键作为开头。记住，make并不管命令是怎么工作的，他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改
日期，如果prerequisites文件的日期要比targets文件的日期要新，或者target不存在的话，那么，make就会执行后续定义的命
令。</font></p>
<p><font face="Courier New">这里要说明一点的是，clean不是一个文件，它只不过是一个动作名字，有点像C语言中的
lable一样，其冒号后什么也没有，那么，make就不会自动去找文件的依赖性，也就不会自动执行其后所定义的命令。要执行其后的命令，就要在make
命令后明显得指出这个lable的名字。这样的方法非常有用，我们可以在一个makefile中定义不用的编译或是和编译无关的命令，比如程序的打包，程
序的备份，等等。</font></p>
</span>
<p><font face="Courier New"><strong>三、make是如何工作的</strong></font></p>
<p><font face="Courier New">在默认的方式下，也就是我们只输入make命令。那么，</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 1、make会在当前目录下找名字叫&#8220;Makefile&#8221;或&#8220;makefile&#8221;的文件。<br>&nbsp;&nbsp;&nbsp; 2、如果找到，它会找文件中的第一个目标文件（target），在上面的例子中，他会找到&#8220;edit&#8221;这个文件，并把这个文件作为最终的目标文件。<br>&nbsp;&nbsp;&nbsp; 3、如果edit文件不存在，或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新，那么，他就会执行后面所定义的命令来生成edit这个文件。<br>&nbsp;&nbsp;&nbsp; 4、如果edit所依赖的.o文件也存在，那么make会在当前文件中找目标为.o文件的依赖性，如果找到则再根据那一个规则生成.o文件。（这有点像一个堆栈的过程）<br>&nbsp;&nbsp;&nbsp; 5、当然，你的C文件和H文件是存在的啦，于是make会生成 .o 文件，然后再用 .o 文件生命make的终极任务，也就是执行文件edit了。</font></p>
<p><font face="Courier New">这就是整个make的依赖性，make会一层又一层地去找文件的依赖关系，直到最终编译出第一
个目标文件。在找寻的过程中，如果出现错误，比如最后被依赖的文件找不到，那么make就会直接退出，并报错，而对于所定义的命令的错误，或是编译不成
功，make根本不理。make只管文件的依赖性，即，如果在我找了依赖关系之后，冒号后面的文件还是不在，那么对不起，我就不工作啦。</font></p>
<p><font face="Courier New">通过上述分析，我们知道，像clean这种，没有被第一个目标文件直接或间接关联，那么它后面所定义的命令将不会被自动执行，不过，我们可以显示要make执行。即命令——&#8220;make clean&#8221;，以此来清除所有的目标文件，以便重编译。</font></p>
<p><font face="Courier New">于是在我们编程中，如果这个工程已被编译过了，当我们修改了其中一个源文件，比如
file.c，那么根据我们的依赖性，我们的目标file.o会被重编译（也就是在这个依性关系后面所定义的命令），于是file.o的文件也是最新的
啦，于是file.o的文件修改时间要比edit要新，所以edit也会被重新链接了（详见edit目标文件后定义的命令）。</font></p>
<p><font face="Courier New">而如果我们改变了&#8220;command.h&#8221;，那么，kdb.o、command.o和files.o都会被重编译，并且，edit会被重链接。</font></p>
<p><br><font face="Courier New"><strong>四、makefile中使用变量</strong></font></p>
<p><font face="Courier New">在上面的例子中，先让我们看看edit的规则：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; edit : main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o edit main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o</font></p>
<p><font face="Courier New">我们可以看到[.o]文件的字符串被重复了两次，如果我们的工程需要加入一个新的[.o]文
件，那么我们需要在两个地方加（应该是三个地方，还有一个地方在clean中）。当然，我们的makefile并不复杂，所以在两个地方加也不累，但如果
makefile变得复杂，那么我们就有可能会忘掉一个需要加入的地方，而导致编译失败。所以，为了makefile的易维护，在makefile中我们
可以使用变量。makefile的变量也就是一个字符串，理解成C语言中的宏可能会更好。</font></p>
<p><font face="Courier New">比如，我们声明一个变量，叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ，反正不管什么啦，只要能够表示obj文件就行了。我们在makefile一开始就这样定义：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp; objects = main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o</font></p>
<p><font face="Courier New">于是，我们就可以很方便地在我们的makefile中以&#8220;$(objects)&#8221;的方式来使用这个变量了，于是我们的改良版makefile就变成下面这个样子：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; objects = main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; edit : $(objects)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o edit $(objects)<br>&nbsp;&nbsp;&nbsp; main.o : main.c defs.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c main.c<br>&nbsp;&nbsp;&nbsp; kbd.o : kbd.c defs.h command.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c kbd.c<br>&nbsp;&nbsp;&nbsp; command.o : command.c defs.h command.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c command.c<br>&nbsp;&nbsp;&nbsp; display.o : display.c defs.h buffer.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c display.c<br>&nbsp;&nbsp;&nbsp; insert.o : insert.c defs.h buffer.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c insert.c<br>&nbsp;&nbsp;&nbsp; search.o : search.c defs.h buffer.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c search.c<br>&nbsp;&nbsp;&nbsp; files.o : files.c defs.h buffer.h command.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c files.c<br>&nbsp;&nbsp;&nbsp; utils.o : utils.c defs.h<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -c utils.c<br>&nbsp;&nbsp;&nbsp; clean :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm edit $(objects)</font></p>
<p><font face="Courier New"><br>于是如果有新的 .o 文件加入，我们只需简单地修改一下 objects 变量就可以了。</font></p>
<p><font face="Courier New">关于变量更多的话题，我会在后续给你一一道来。</font></p>
<p><br><font face="Courier New"><strong>五、让make自动推导</strong></font></p>
<p><font face="Courier New">GNU的make很强大，它可以自动推导文件以及文件依赖关系后面的命令，于是我们就没必要去在每一个[.o]文件后都写上类似的命令，因为，我们的make会自动识别，并自己推导命令。</font></p>
<p><font face="Courier New">只要make看到一个[.o]文件，它就会自动的把[.c]文件加在依赖关系中，如果make
找到一个whatever.o，那么whatever.c，就会是whatever.o的依赖文件。并且 cc -c whatever.c
也会被推导出来，于是，我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。</font></p>
<p><br><font face="Courier New">&nbsp;&nbsp;&nbsp; objects = main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; edit : $(objects)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o edit $(objects)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; main.o : defs.h<br>&nbsp;&nbsp;&nbsp; kbd.o : defs.h command.h<br>&nbsp;&nbsp;&nbsp; command.o : defs.h command.h<br>&nbsp;&nbsp;&nbsp; display.o : defs.h buffer.h<br>&nbsp;&nbsp;&nbsp; insert.o : defs.h buffer.h<br>&nbsp;&nbsp;&nbsp; search.o : defs.h buffer.h<br>&nbsp;&nbsp;&nbsp; files.o : defs.h buffer.h command.h<br>&nbsp;&nbsp;&nbsp; utils.o : defs.h</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; .PHONY : clean<br>&nbsp;&nbsp;&nbsp; clean :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm edit $(objects)</font></p>
<p><font face="Courier New">这种方法，也就是make的&#8220;隐晦规则&#8221;。上面文件内容中，&#8220;.PHONY&#8221;表示，clean是个伪目标文件。</font></p>
<p><font face="Courier New">关于更为详细的&#8220;隐晦规则&#8221;和&#8220;伪目标文件&#8221;，我会在后续给你一一道来。</font></p>
<p><br><font face="Courier New"><strong>六、另类风格的makefile</strong></font></p>
<p><font face="Courier New">即然我们的make可以自动推导命令，那么我看到那堆[.o]和[.h]的依赖就有点不爽，那么多的重复的[.h]，能不能把其收拢起来，好吧，没有问题，这个对于make来说很容易，谁叫它提供了自动推导命令和文件的功能呢？来看看最新风格的makefile吧。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; objects = main.o kbd.o command.o display.o \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert.o search.o files.o utils.o</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; edit : $(objects)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cc -o edit $(objects)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; $(objects) : defs.h<br>&nbsp;&nbsp;&nbsp; kbd.o command.o files.o : command.h<br>&nbsp;&nbsp;&nbsp; display.o insert.o search.o files.o : buffer.h</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; .PHONY : clean<br>&nbsp;&nbsp;&nbsp; clean :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm edit $(objects)</font></p>
<p><font face="Courier New">这种风格，让我们的makefile变得很简单，但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的，一是文件的依赖关系看不清楚，二是如果文件一多，要加入几个新的.o文件，那就理不清楚了。</font></p>
<p><br><font face="Courier New"><strong>七、清空目标文件的规则</strong></font></p>
<p><font face="Courier New">每个Makefile中都应该写一个清空目标文件（.o和执行文件）的规则，这不仅便于重编译，也很利于保持文件的清洁。这是一个&#8220;修养&#8221;（呵呵，还记得我的《编程修养》吗）。一般的风格都是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; clean:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rm edit $(objects)</font></p>
<p><font face="Courier New">更为稳健的做法是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .PHONY : clean<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; clean :<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -rm edit $(objects)</font></p>
<p><font face="Courier New">前面说过，.PHONY意思表示clean是一个&#8220;伪目标&#8221;，。而在rm命令前面加了一个小减
号的意思就是，也许某些文件出现问题，但不要管，继续做后面的事。当然，clean的规则不要放在文件的开头，不然，这就会变成make的默认目标，相信
谁也不愿意这样。不成文的规矩是——&#8220;clean从来都是放在文件的最后&#8221;。</font></p>
<p><br><font face="Courier New">上面就是一个makefile的概貌，也是makefile的基础，下面还有很多makefile的相关细节，准备好了吗？准备好了就来。</font></p>
<p><strong><font face="Courier New">一、Makefile里有什么？</font></strong></p>
<p><font face="Courier New">Makefile里主要包含了五个东西：显式规则、隐晦规则、变量定义、文件指示和注释。</font></p>
<p><font face="Courier New">1、显式规则。显式规则说明了，如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出，要生成的文件，文件的依赖文件，生成的命令。</font></p>
<p><font face="Courier New">2、隐晦规则。由于我们的make有自动推导的功能，所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile，这是由make所支持的。</font></p>
<p><font face="Courier New">3、变量的定义。在Makefile中我们要定义一系列的变量，变量一般都是字符串，这个有点你C语言中的宏，当Makefile被执行时，其中的变量都会被扩展到相应的引用位置上。</font></p>
<p><font face="Courier New">4、文件指示。其包括了三个部分，一个是在一个Makefile中引用另一个
Makefile，就像C语言中的include一样；另一个是指根据某些情况指定Makefile中的有效部分，就像C语言中的预编译#if一样；还有
就是定义一个多行的命令。有关这一部分的内容，我会在后续的部分中讲述。</font></p>
<p><font face="Courier New">5、注释。Makefile中只有行注释，和UNIX的Shell脚本一样，其注释是用&#8220;#&#8221;字符，这个就像C/C++中的&#8220;//&#8221;一样。如果你要在你的Makefile中使用&#8220;#&#8221;字符，可以用反斜框进行转义，如：&#8220;\#&#8221;。</font></p>
<p><font face="Courier New">最后，还值得一提的是，在Makefile中的命令，必须要以[Tab]键开始。</font></p>
<p><br><strong><font face="Courier New">二、Makefile的文件名</font></strong></p>
<p><font face="Courier New">默认的情况下，make命令会在当前目录下按顺序找寻文件名为&#8220;GNUmakefile&#8221;、
&#8220;makefile&#8221;、&#8220;Makefile&#8221;的文件，找到了解释这个文件。在这三个文件名中，最好使用&#8220;Makefile&#8221;这个文件名，因为，这个文件名
第一个字符为大写，这样有一种显目的感觉。最好不要用&#8220;GNUmakefile&#8221;，这个文件是GNU的make识别的。有另外一些make只对全小写的
&#8220;makefile&#8221;文件名敏感，但是基本上来说，大多数的make都支持&#8220;makefile&#8221;和&#8220;Makefile&#8221;这两种默认文件名。</font></p>
<p><font face="Courier New">当然，你可以使用别的文件名来书写Makefile，比
如：&#8220;Make.Linux&#8221;，&#8220;Make.Solaris&#8221;，&#8220;Make.AIX&#8221;等，如果要指定特定的Makefile，你可以使用make的&#8220;-
f&#8221;和&#8220;--file&#8221;参数，如：make -f Make.Linux或make --file Make.AIX。</font></p>
<p><br><strong><font face="Courier New">三、引用其它的Makefile</font></strong></p>
<p><font face="Courier New">在Makefile使用include关键字可以把别的Makefile包含进来，这很像C语言的#include，被包含的文件会原模原样的放在当前文件的包含位置。include的语法是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; include &lt;filename&gt;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; filename可以是当前操作系统Shell的文件模式（可以保含路径和通配符）</font></p>
<p><font face="Courier New">在include前面可以有一些空字符，但是绝不能是[Tab]键开始。include
和&lt;filename&gt;可以用一个或多个空格隔开。举个例子，你有这样几个Makefile：a.mk、b.mk、c.mk，还有一个文件叫
foo.make，以及一个变量$(bar)，其包含了e.mk和f.mk，那么，下面的语句：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; include foo.make *.mk $(bar)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 等价于：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; include foo.make a.mk b.mk c.mk e.mk f.mk</font></p>
<p><font face="Courier New">make命令开始时，会把找寻include所指出的其它Makefile，并把其内容安置在
当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话，make会在当前目录下首先寻找，如果当前目录
下没有找到，那么，make还会在下面的几个目录下找：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 1、如果make执行时，有&#8220;-I&#8221;或&#8220;--include-dir&#8221;参数，那么make就会在这个参数所指定的目录下去寻找。<br>&nbsp;&nbsp;&nbsp; 2、如果目录&lt;prefix&gt;/include（一般是：/usr/local/bin或/usr/include）存在的话，make也会去找。</font></p>
<p><font face="Courier New">如果有文件没有找到的话，make会生成一条警告信息，但不会马上出现致命错误。它会继续载入
其它的文件，一旦完成makefile的读取，make会再重试这些没有找到，或是不能读取的文件，如果还是不行，make才会出现一条致命信息。如果你
想让make不理那些无法读取的文件，而继续执行，你可以在include前加一个减号&#8220;-&#8221;。如：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; -include &lt;filename&gt;<br>&nbsp;&nbsp;&nbsp; 其表示，无论include过程中出现什么错误，都不要报错继续执行。和其它版本make兼容的相关命令是sinclude，其作用和这一个是一样的。</font></p>
<p><br><font face="Courier New"><strong>四、环境变量 MAKEFILES</strong> </font></p>
<p><font face="Courier New">如果你的当前环境中定义了环境变量MAKEFILES，那么，make会把这个变量中的值做一
个类似于include的动作。这个变量中的值是其它的Makefile，用空格分隔。只是，它和include不同的是，从这个环境变中引入的
Makefile的&#8220;目标&#8221;不会起作用，如果环境变量中定义的文件发现错误，make也会不理。</font></p>
<p><font face="Courier New">但是在这里我还是建议不要使用这个环境变量，因为只要这个变量一被定义，那么当你使用make
时，所有的Makefile都会受到它的影响，这绝不是你想看到的。在这里提这个事，只是为了告诉大家，也许有时候你的Makefile出现了怪事，那么
你可以看看当前环境中有没有定义这个变量。</font></p>
<p><br><strong><font face="Courier New">五、make的工作方式</font></strong></p>
<p><font face="Courier New">GNU的make工作时的执行步骤入下：（想来其它的make也是类似）</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 1、读入所有的Makefile。<br>&nbsp;&nbsp;&nbsp; 2、读入被include的其它Makefile。<br>&nbsp;&nbsp;&nbsp; 3、初始化文件中的变量。<br>&nbsp;&nbsp;&nbsp; 4、推导隐晦规则，并分析所有规则。<br>&nbsp;&nbsp;&nbsp; 5、为所有的目标文件创建依赖关系链。<br>&nbsp;&nbsp;&nbsp; 6、根据依赖关系，决定哪些目标要重新生成。<br>&nbsp;&nbsp;&nbsp; 7、执行生成命令。</font></p>
<p><font face="Courier New">1-5步为第一个阶段，6-7为第二个阶段。第一个阶段中，如果定义的变量被使用了，那
么，make会把其展开在使用的位置。但make并不会完全马上展开，make使用的是拖延战术，如果变量出现在依赖关系的规则中，那么仅当这条依赖被决
定要使用了，变量才会在其内部展开。</font></p>
<p><font face="Courier New">当然，这个工作方式你不一定要清楚，但是知道这个方式你也会对make更为熟悉。有了这个基础，后续部分也就容易看懂了。</font></p>
<span id="ArticleContent1_ArticleContent1_lblContent"></span><br><br><img src ="http://www.cppblog.com/yehongly/aggbug/62197.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-09-18 17:39 <a href="http://www.cppblog.com/yehongly/archive/2008/09/18/62197.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++预处理过程与语句总结</title><link>http://www.cppblog.com/yehongly/archive/2008/01/03/40308.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Thu, 03 Jan 2008 05:59:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/01/03/40308.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/40308.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/01/03/40308.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/40308.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/40308.html</trackback:ping><description><![CDATA[转载请保留: <a href="http://www.cnscn.org/" target="_blank"><font size="2" color="#2f5fa1">http://www.cnscn.org</font></a>(<a href="http://www.cnscn.org/" target="_blank"><font size="2" color="#2f5fa1">CNS电脑与英语学习网</font></a>)<br><span style="font-weight: bold;"><font size="2">Author: cnscn &lt;</font><a href="http://www.cnscn.org&gt;/" target="_blank"><font size="2" color="#2f5fa1">http://www.cnscn.org&gt;</font></a><br><br><font size="2">1)预处理</font></span><br>&nbsp; 根据已放置在文件中的预处理指令来修改源文件的
<script type="text/javascript">myshowbaidu('%C4%DA%C8%DD',2,'内容');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%C4%DA%C8%DD&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">内容</font></a><br>&nbsp; 预处理器会分析\执行所有的预处理器指令,然后删除他们,得到一个仅包含C++语句的转换单元<br>&nbsp; 预处理指令以#号开头<br><br><br>&nbsp; 常用的预处理指令:<br>&nbsp; #include&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 包含头文件<br><br>&nbsp; #if&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 条件<br>&nbsp; #else&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 否则<br>&nbsp; #elif&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 否则如果<br>&nbsp; #endif&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 结束条件<br><br>&nbsp; #ifdef&nbsp; 或 #if defined&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果定义了一个符号, 就执行操作<br>&nbsp; #ifndef 或 #if !defined&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果没有定义一个符号, 就指执行操作<br><br>&nbsp; #define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 定义一个符号<br>&nbsp; #undef&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 删除一个符号<br><br>&nbsp; #line&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; 重新定义当前行号和文件名<br><br>&nbsp; #error&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; 输出编译错误
<script type="text/javascript">myshowbaidu('%CF%FB%CF%A2',2,'消息');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%CF%FB%CF%A2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">消息</font></a>,　停止编译<br><br>&nbsp; #pragma&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 提供
<script type="text/javascript">myshowbaidu('%BB%FA%C6%F7',2,'机器');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%BB%FA%C6%F7&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">机器</font></a>专用的特性,同时保证与C++的完全兼容<br><br><br><span style="font-weight: bold;"><font size="2">2)#include　 在
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
</font><a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a><font size="2">中包含头文件</font></span><br>　头文件通常以.h结尾,其
<script type="text/javascript">myshowbaidu('%C4%DA%C8%DD',2,'内容');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%C4%DA%C8%DD&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">内容</font></a>可使用#include预处理器指令包含到
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a>中<br>　头文件中一般包含: 函数原型与全局变量<br><br>&nbsp; 形式常有下面两种<br>&nbsp; #include &lt;iostream&gt;<br>&nbsp; #include "myheader.h"<br><br>&nbsp; 前者&lt;&gt;用来引用标准库头文件,后者""常用来引用自定义的头文件<br>&nbsp; 前者&lt;&gt;编译器只搜索包含标准库头文件的默认
<script type="text/javascript">myshowbaidu('%C4%BF%C2%BC',2,'目录');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%C4%BF%C2%BC&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">目录</font></a>,后者首先搜索正在编译的源文件所在的
<script type="text/javascript">myshowbaidu('%C4%BF%C2%BC',2,'目录');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%C4%BF%C2%BC&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">目录</font></a>,找不到时再搜索包含标准库头文件的默认
<script type="text/javascript">myshowbaidu('%C4%BF%C2%BC',2,'目录');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%C4%BF%C2%BC&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">目录</font></a>.<br>&nbsp; 如果把头文件放在其他
<script type="text/javascript">myshowbaidu('%C4%BF%C2%BC',2,'目录');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%C4%BF%C2%BC&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">目录</font></a>下,为了查找到它,必须在双引号中指定从源文件到头文件的完整路径<br><br><br><font size="2"><span style="font-weight: bold;">3)#define&nbsp; 定义符号、宏</span><br><span style="font-weight: bold;">1&gt;符号</span><br></font>&nbsp; #define PI 3.1415925&nbsp; 定义符号PI为3.1415925<br>&nbsp; #define PI　　　　　　取消PI的值<br><br>&nbsp; 这里PI看起来像一个变量，但它与变量没有任何关系，它只是一个符号或标志，在
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a>代码编译前，此符号会用一组指定的字符来代替<br>&nbsp; 3.14159265　不是一个数值，只是一个字符串，不会进行检查<br><br>&nbsp; 在编译前，预处理器会遍历代码，在它认为置换有意义的地方，用字符串PI的定义值(3.14159265)来代替<br>　在注释或字符串中的PI不进行替换<br><br>&nbsp; 在C中常以#define来定义符号常量，但在C++中最好使用const 来定义常量<br>&nbsp; #define PI 3.14159265<br>&nbsp; const long double PI=3.14159265;<br>&nbsp; 两者比较下，前者没有类型的指定容易引起不必须的麻烦，而后者定义清楚，所以在C++中推荐使用const来定义常量<br><br>　#define的缺点:<br>&nbsp;&nbsp; 1)不支持类型检查<br>&nbsp;&nbsp; 2)不考虑作用域<br>&nbsp;&nbsp; 3)符号名不能限制在一个命名
<script type="text/javascript">myshowbaidu('%BF%D5%BC%E4',2,'空间');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%BF%D5%BC%E4&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">空间</font></a>中<br><br><br><br><font size="2"><span style="font-weight: bold;">2&gt;#undef 删除#define定义的符号</span><br></font>&nbsp; #define PI 3.14159265<br>&nbsp; ... //之间所有的PI都可以被替换为3.14159265<br><br>&nbsp; #undef PI<br>&nbsp; 之后不再有PI这个标识符<br><br><br><font size="2"><span style="font-weight: bold;">3&gt;定义宏</span><br></font>&nbsp; #define Print(Var) count&lt;&lt;(Var)&lt;&lt;endl<br>&nbsp; 用宏名中的参数带入语句中的参数<br>&nbsp; 宏后面没有;号<br>&nbsp; Print(Var)中的Print和(之间不能有空格，否则(就会被解释为置换字符串的一部分<br><br>&nbsp; #define Print(Var, digits)&nbsp; count &lt;&lt; setw(digits) &lt;&lt; (Var) &lt;&lt; endl<br>&nbsp; 调用<br>&nbsp; Print(ival, 15)<br>&nbsp; 预处理器就会把它换成<br>&nbsp; cout &lt;&lt; setw(15) &lt;&lt; (ival) &lt;&lt; endl;<br><br><br>&nbsp; 所有的情况下都可以使用内联函数来代替宏，这样可以增强类型的检查<br>&nbsp; template&lt;class T&gt; inline void Print (const T&amp; var, const int&amp; digits)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count&lt;&lt;setw(digits)&lt;&lt;var&lt;&lt;endl;<br>&nbsp; }<br><br>&nbsp; 调用<br>&nbsp; Print(ival, 15);<br><br><br>&nbsp; 使用宏时应注意的易引起的错误:<br>&nbsp; #define max(x,y) x&gt;y?x:y;+<br><br><br>&nbsp; 调用 result = max(myval, 99);&nbsp; 则换成 result = myval&gt;99?myval:99;&nbsp; 这个没有问题是正确的<br>&nbsp; 调用 result = max(myval++, 99);&nbsp; 则换成 result = myval++&gt;99?myval++:99; 这样如果myval&gt;99那么myval就会递增两次，这种情况下()是没什么用的如result=max((x),y)则 result = (myval++)&gt;99?(myval++):99;<br><br>&nbsp; 再如<br>&nbsp; #define product(m,n) m*n<br><br>&nbsp; 调用<br>&nbsp; result = product(5+1,6);则替换为result = 5+1*6; 所以产生了错误的结果，此时应使用()把参数括起<br>&nbsp; #define product(m,n) (m)*(n)<br>&nbsp; 则result = product(5+1,6);则替换为result = (5+1)*(6); 所以产生了错误的结果，此时应使用()把参数括起<br><br><br>结论:　一般用内联函数来代替预处理器宏<br><br><br><span style="font-weight: bold;">
<script type="text/javascript">myshowbaidu('%BC%BC%C7%C9',2,'技巧');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%BC%BC%C7%C9&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">技巧</font></a><font size="2">:</font></span><br>&nbsp;&nbsp;&nbsp; <font size="2"><span style="font-weight: bold;">1)给替换变量加引号</span><br></font>&nbsp;&nbsp;&nbsp; #define MYSTR "I love you"<br><br>&nbsp;&nbsp;&nbsp; cout &lt;&lt; MYSTR ; //I love you而不是"I love you"<br>&nbsp;&nbsp;&nbsp; 如果<br>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "MYSTR" ; //则会输出"MYSTR"而不是"I love you"<br><br>&nbsp;&nbsp;&nbsp; 可以这样做<br>&nbsp;&nbsp;&nbsp; cout &lt;&lt; #MYSTR ;&nbsp; //则会输出 "I love you"即cout &lt;&lt; "\"I love you\"";<br><br>&nbsp;&nbsp;&nbsp; <font size="2"><span style="font-weight: bold;">2)在宏表达式中连接几个参数</span><br></font>&nbsp;&nbsp;&nbsp; 如<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #define join(a,b) ab　这样不会理解为参数a的值与参数b的值的连接，即如join(10,999)不会理解为10999而是把ab理解为字符串，即输出ab<br>&nbsp;&nbsp;&nbsp; 这时可以<br>&nbsp;&nbsp;&nbsp; #define join(a,b) a##b<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 则join(10,999)就会输出10999<br><br><br><font size="2"><span style="font-weight: bold;">3)逻辑预处理器指令</span><br></font>　#if defined CALCAVERAGE　或 #ifdef CALCAVERAGE<br>&nbsp;&nbsp; int count=sizeof(data)/sizeof(data[0]);<br>&nbsp;&nbsp; for(int i=0; i&lt;count; i++)<br>&nbsp;&nbsp;&nbsp;&nbsp; average += data<em>;<br>&nbsp;&nbsp; average /= count;<br>&nbsp; #endif<br><br>&nbsp; 如果已经定义符号CALCAVERAGE则把#if与#endif间的语句放在要编译的源代码内<br><br><br>&nbsp; 防止重复引入某些头文件<br>&nbsp; #ifndef COMPARE_H<br>&nbsp; #define COMPARE_H&nbsp;&nbsp;&nbsp;&nbsp; 注意: 这里只是定义一个没有值的符号COMPARE_H, 下面的namespace compare不是COMPARE_H的
<script type="text/javascript">myshowbaidu('%C4%DA%C8%DD',2,'内容');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%C4%DA%C8%DD&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">内容</font></a>，这里的定义不像是定义一个常量或宏，仅仅定义一个符号，指出此符号已定义，则就会有下面的
<script type="text/javascript">myshowbaidu('%C4%DA%C8%DD',2,'内容');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%C4%DA%C8%DD&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">内容</font></a>namespace compare{...<br>&nbsp;&nbsp; namespace compare{<br>&nbsp;&nbsp;&nbsp;&nbsp; double max(const double* data, int size);<br>&nbsp;&nbsp;&nbsp;&nbsp; double min(const double* data, int size);<br>&nbsp;&nbsp; }<br>&nbsp; #endif<br><br>&nbsp; 比较<br>&nbsp; #define VERSION \<br>&nbsp;&nbsp; 3<br>&nbsp; 因为有换行符\　所以上句等价于 #define VERSION 3<br>&nbsp; 由此可以看出#define COMPARE_H与namespace compare是独立没有关系的两个行<br><br><br>&nbsp; 也可以这样用<br>&nbsp; #if defined block1 &amp;&amp; defined block2<br>&nbsp; ...<br>&nbsp; #endif<br><br>&nbsp; #if CPU==PENTIUM4<br>&nbsp;&nbsp;&nbsp; ...<br>&nbsp; #endif<br><br><br>&nbsp; #if LANGUAGE == ENGLISH<br>&nbsp; #define Greeting "Good Morning."<br>&nbsp; #elif LANGUAGE == GERMAN<br>&nbsp; #define Greeting "Guten Tag."<br>&nbsp; #elif LANGUAGE == FRENCH<br>&nbsp; #define Greeting "Bonjour."<br>&nbsp; #else<br>&nbsp; #define Greeting "Hi."<br>&nbsp; #endif<br>&nbsp; std::cout&lt;&lt;Greeting &lt;&lt; std::endl;<br><br><br>&nbsp; #if VERSION == 3<br>&nbsp; ...<br>&nbsp; #elif VERSION == 4<br>&nbsp; ...<br>&nbsp; #else<br>&nbsp; ...<br>&nbsp; #endif<br><br><br><font size="2"><span style="font-weight: bold;">5)标准的预处理器宏</span><br></font>&nbsp; __LINE__&nbsp;&nbsp;&nbsp;&nbsp; 当前源文件中的代码行号，十进制整数<br>&nbsp; __FILE__　&nbsp; 源文件的名称，字符串字面量<br>&nbsp; __DATE__　 源文件的处理日期，字符串字面量，格式mmm dd yyyy其中mmm是月份如Jan、Feb等　dd是01-31 yyyy是四位的年份<br>&nbsp; __TIME__&nbsp;&nbsp;&nbsp; 源文件的编译
<script type="text/javascript">myshowbaidu('%CA%B1%BC%E4',2,'时间');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%CA%B1%BC%E4&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">时间</font></a>，也是字符串字面量格式是hh:mm:ss<br>&nbsp; __STDC__&nbsp;&nbsp; 这取决于实现方式，如果编译器选项设置为编译标准的C代码，通常就定义它，否则就不定义它<br>&nbsp; __cplusplus&nbsp; 在编译C++
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a>时，它就定义为199711L<br><br>&nbsp; 使用#line可以修改__FILE__返回的字符串<br>&nbsp; 如<br>&nbsp; #line 1000&nbsp;&nbsp;&nbsp; 把当前行号设置为1000<br>&nbsp; #line 1000 "the program file"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 修改__FILE__返回的字符串行号改为了1000，文件名改为了"the program file"<br>&nbsp; #line __LINE__ "the program file"&nbsp; 修改__FILE__返回的字符串行号没变，文件名改为了"the program file"<br><br>&nbsp; cout &lt;&lt; "program last complied at "&lt;&lt;__TIME__<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;&lt; " on " &lt;&lt; __DATE__<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;&lt; endl;<br><br><br><font size="2"><span style="font-weight: bold;">6)#error</span><br></font>&nbsp; 在预处理阶段，如果出现了错误，则#error指令可以生成一个诊断
<script type="text/javascript">myshowbaidu('%CF%FB%CF%A2',2,'消息');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%CF%FB%CF%A2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">消息</font></a>，并显示为一个编译错误，同时中止编译<br>&nbsp; #ifndef __cplusplus<br>&nbsp; #error "Error -&nbsp; Should be C++"<br>&nbsp; #endif<br><br><br><font size="2"><span style="font-weight: bold;">7)#pragma</span><br></font>　专门用于实现预先定义好的选项，其结果在编译器说明文档中进行了详细的解释。编译器未识别出来的#pragma指令都会被忽略<br><br><br><font size="2"><span style="font-weight: bold;">8)assert()宏</span><br></font>&nbsp; 在标准库头文件&lt;cassert&gt;中声明<br>&nbsp; 用于在
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a>中
<script type="text/javascript">myshowbaidu('%B2%E2%CA%D4',2,'测试');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B2%E2%CA%D4&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">测试</font></a>一个逻辑表达式，如果逻辑表达式为false, 则assert()会终止
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a>，并显示诊断
<script type="text/javascript">myshowbaidu('%CF%FB%CF%A2',2,'消息');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%CF%FB%CF%A2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">消息</font></a><br>&nbsp; 用于在条件不满足就会出现重大错误，所以应确保后面的语句不应再继续执行，所以它的应用非常灵活<br>&nbsp; 注意: assert不是错误处理
<script type="text/javascript">myshowbaidu('%BB%FA%D6%C6',2,'机制');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%BB%FA%D6%C6&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">机制</font></a>，逻辑表达式的结果不应产生负面效果，也不应超出
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a>员的控制(如找开一个文件是否成功),
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a>应提供适当的代码来处理这种情况<br>　assert(expression);<br>&nbsp; assert(expression) &amp;&amp; assert(expression2);<br>&nbsp; 可以使用#define NDEBUG来关闭断言
<script type="text/javascript">myshowbaidu('%BB%FA%D6%C6',2,'机制');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%BB%FA%D6%C6&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">机制</font></a><br><br>&nbsp; #include &lt;iostream&gt;<br>&nbsp; #include &lt;cassert&gt;<br>&nbsp; using std::cout;<br>&nbsp; using std::endl;<br><br>&nbsp; int main()<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; int x=0;<br>&nbsp;&nbsp;&nbsp;&nbsp; int y=0;<br><br>&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;endl;<br><br>&nbsp;&nbsp;&nbsp;&nbsp; for(x=0; x&lt;20; x++)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"x= "&lt;&lt;x &lt;&lt;" y= "&lt;&lt;y&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; assert(x&lt;y);　//当x&gt;=y与x==5时，就报错，并终止
<script type="text/javascript">myshowbaidu('%B3%CC%D0%F2',2,'程序');</script>
<a class="my_green_normal" href="http://www.baidu.com/s?tn=cnscn_pg&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=%B3%CC%D0%F2&amp;_si=%CB%D1%CB%F7" target="_blank"><font size="2" color="#2f5fa1">程序</font></a>的执行<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp; }</em> <img src ="http://www.cppblog.com/yehongly/aggbug/40308.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-01-03 13:59 <a href="http://www.cppblog.com/yehongly/archive/2008/01/03/40308.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>