﻿<?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>IT博客-游子的博客</title><link>http://www.cnitblog.com/liaoqingshan/</link><description>慈母手中线，游子身上衣，
临行密密缝，意恐迟迟归，
谁言寸草心，报得三春晖。


数据读取中，请稍候......</description><language>zh-cn</language><lastBuildDate>Sun, 05 Jul 2009 02:41:09 GMT</lastBuildDate><pubDate>Sun, 05 Jul 2009 02:41:09 GMT</pubDate><ttl>60</ttl><item><title>一个很好的makefile教程4（转载）</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59815.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Fri, 03 Jul 2009 08:52:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59815.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59815.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59815.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59815.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59815.html</trackback:ping><description><![CDATA[<p><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font size=2>隐含规则<br>————<br><br>在我们使用Makefile时，有一些我们会经常使用，而且使用频率非常高的东西，比如，我们编译C/C++的源程序为中间目标文件（Unix下是[.o] 文件，Windows下是[.obj]文件）。本章讲述的就是一些在Makefile中的&#8220;隐含的&#8221;，早先约定了的，不需要我们再写出来的规则。<br><br>&#8220;隐含规则&#8221;也就是一种惯例，make会按照这种&#8220;惯例&#8221;心照不喧地来运行，那怕我们的Makefile中没有书写这样的规则。例如，把[.c]文件编译成[.o]文件这一规则，你根本就不用写出来，make会自动推导出这种规则，并生成我们需要的[.o]文件。<br><br>&#8220;隐含规则&#8221;会使用一些我们系统变量，我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量&#8220;CFLAGS&#8221;可以控制编译时的编译器参数。<br><br>我们还可以通过&#8220;模式规则&#8221;的方式写下自己的隐含规则。用&#8220;后缀规则&#8221;来定义隐含规则会有许多的限制。使用&#8220;模式规则&#8221;会更回得智能和清楚，但&#8220;后缀规则&#8221;可以用来保证我们Makefile的兼容性。<br>我们了解了&#8220;隐含规则&#8221;，可以让其为我们更好的服务，也会让我们知道一些&#8220;约定俗成&#8221;了的东西，而不至于使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当然，任何事物都是矛盾的，水能载舟，亦可覆舟，所以，有时候&#8220;隐含规则&#8221;也会给我们造成不小的麻烦。只有了解了它，我们才能更好地使用它。<br><br><br>一、使用隐含规则<br><br>如果要使用隐含规则生成你需要的目标，你所需要做的就是不要写出这个目标的规则。那么， make会试图去自动推导产生这个目标的规则和命令，如果make可以自动推导生成这个目标的规则和命令，那么这个行为就是隐含规则的自动推导。当然，隐含规则是make事先约定好的一些东西。例如，我们有下面的一个Makefile：<br><br>foo : foo.o bar.o<br>cc &#8211;o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)<br><br>我们可以注意到，这个Makefile中并没有写下如何生成foo.o和bar.o这两目标的规则和命令。因为make的&#8220;隐含规则&#8221;功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。<br><br>make 会在自己的&#8220;隐含规则&#8221;库中寻找可以用的规则，如果找到，那么就会使用。如果找不到，那么就会报错。在上面的那个例子中，make调用的隐含规则是，把 [.o]的目标的依赖文件置成[.c]，并使用C的编译命令&#8220;cc &#8211;c $(CFLAGS) [.c]&#8221;来生成[.o]的目标。也就是说，我们完全没有必要写下下面的两条规则：<br><br>foo.o : foo.c<br>cc &#8211;c foo.c $(CFLAGS)<br>bar.o : bar.c<br>cc &#8211;c bar.c $(CFLAGS)<br><br>因为，这已经是&#8220;约定&#8221;好了的事了，make和我们约定好了用C编译器&#8220;cc&#8221;生成[.o]文件的规则，这就是隐含规则。<br><br>当然，如果我们为[.o]文件书写了自己的规则，那么make就不会自动推导并调用隐含规则，它会按照我们写好的规则忠实地执行。<br><br>还有，在make的&#8220;隐含规则库&#8221;中，每一条隐含规则都在库中有其顺序，越靠前的则是越被经常使用的，所以，这会导致我们有些时候即使我们显示地指定了目标依赖，make也不会管。如下面这条规则（没有命令）：<br><br>foo.o : foo.p<br><br>依赖文件&#8220;foo.p&#8221;（Pascal程序的源文件）有可能变得没有意义。如果目录下存在了&#8220;foo.c&#8221;文件，那么我们的隐含规则一样会生效，并会通过 &#8220;foo.c&#8221;调用C的编译器生成foo.o文件。因为，在隐含规则中，Pascal的规则出现在C的规则之后，所以，make找到可以生成foo.o的 C的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导，那么，你就不要只写出&#8220;依赖规则&#8221;，而不写命令。<br><br><br>二、隐含规则一览<br><br>这里我们将讲述所有预先设置（也就是make内建）的隐含规则，如果我们不明确地写下规则，那么，make就会在这些规则中寻找所需要规则和命令。当然，我们也可以使用make的参数&#8220;-r&#8221;或&#8220;--no-builtin-rules&#8221;选项来取消所有的预设置的隐含规则。<br><br>当然，即使是我们指定了&#8220;-r&#8221;参数，某些隐含规则还是会生效，因为有许多的隐含规则都是使用了&#8220;后缀规则&#8221;来定义的，所以，只要隐含规则中有&#8220;后缀列表&#8221; （也就一系统定义在目标.SUFFIXES的依赖目标），那么隐含规则就会生效。默认的后缀列表是：.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具体的细节，我们会在后面讲述。<br><br>还是先来看一看常用的隐含规则吧。<br><br>1、编译C程序的隐含规则。<br>&#8220;&lt;n&gt;.o&#8221;的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.c&#8221;，并且其生成命令是&#8220;$(CC) &#8211;c $(CPPFLAGS) $(CFLAGS)&#8221;<br><br>2、编译C++程序的隐含规则。<br>&#8220;&lt; n&gt;.o&#8221;的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.cc&#8221;或是&#8220;&lt;n&gt;.C&#8221;，并且其生成命令是&#8220;$(CXX) &#8211;c $(CPPFLAGS) $(CFLAGS)&#8221;。（建议使用&#8220;.cc&#8221;作为C++源文件的后缀，而不是&#8220;.C&#8221;）<br><br>3、编译Pascal程序的隐含规则。<br>&#8220;&lt;n&gt;.o&#8221;的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.p&#8221;，并且其生成命令是&#8220;$(PC) &#8211;c $(PFLAGS)&#8221;。<br><br>4、编译Fortran/Ratfor程序的隐含规则。<br>&#8220;&lt;n&gt;.o&#8221;的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.r&#8221;或&#8220;&lt;n&gt;.F&#8221;或&#8220;&lt;n&gt;.f&#8221;，并且其生成命令是:<br>&#8220;.f&#8221; &#8220;$(FC) &#8211;c $(FFLAGS)&#8221;<br>&#8220;.F&#8221; &#8220;$(FC) &#8211;c $(FFLAGS) $(CPPFLAGS)&#8221;<br>&#8220;.f&#8221; &#8220;$(FC) &#8211;c $(FFLAGS) $(RFLAGS)&#8221;<br><br>5、预处理Fortran/Ratfor程序的隐含规则。<br>&#8220;&lt;n&gt;.f&#8221;的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.r&#8221;或&#8220;&lt;n&gt;.F&#8221;。这个规则只是转换Ratfor或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是：<br>&#8220;.F&#8221; &#8220;$(FC) &#8211;F $(CPPFLAGS) $(FFLAGS)&#8221;<br>&#8220;.r&#8221; &#8220;$(FC) &#8211;F $(FFLAGS) $(RFLAGS)&#8221;<br><br>6、编译Modula-2程序的隐含规则。<br>&#8220;&lt; n&gt;.sym&#8221;的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.def&#8221;，并且其生成命令是：&#8220;$(M2C) $(M2FLAGS) $(DEFFLAGS)&#8221;。&#8220;&lt;n.o&gt;&#8221; 的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.mod&#8221;，并且其生成命令是：&#8220;$(M2C) $(M2FLAGS) $(MODFLAGS)&#8221;。<br><br>7、汇编和汇编预处理的隐含规则。<br>&#8220;&lt;n&gt;.o&#8221; 的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.s&#8221;，默认使用编译品&#8220;as&#8221;，并且其生成命令是：&#8220;$(AS) $(ASFLAGS)&#8221;。&#8220;&lt;n&gt;.s&#8221; 的目标的依赖目标会自动推导为&#8220;&lt;n&gt;.S&#8221;，默认使用C预编译器&#8220;cpp&#8221;，并且其生成命令是：&#8220;$(AS) $(ASFLAGS)&#8221;。<br><br>8、链接Object文件的隐含规则。<br>&#8220;&lt; n&gt;&#8221;目标依赖于&#8220;&lt;n&gt;.o&#8221;，通过运行C的编译器来运行链接程序生成（一般是&#8220;ld&#8221;），其生成命令是：&#8220;$(CC) $(LDFLAGS) &lt;n&gt;.o $(LOADLIBES) $(LDLIBS)&#8221;。这个规则对于只有一个源文件的工程有效，同时也对多个Object文件（由不同的源文件生成）的也有效。例如如下规则：<br><br>x : y.o z.o<br><br>并且&#8220;x.c&#8221;、&#8220;y.c&#8221;和&#8220;z.c&#8221;都存在时，隐含规则将执行如下命令：<br><br>cc -c x.c -o x.o<br>cc -c y.c -o y.o<br>cc -c z.c -o z.o<br>cc x.o y.o z.o -o x<br>rm -f x.o<br>rm -f y.o<br>rm -f z.o<br><br>如果没有一个源文件（如上例中的x.c）和你的目标名字（如上例中的x）相关联，那么，你最好写出自己的生成规则，不然，隐含规则会报错的。<br><br>9、Yacc C程序时的隐含规则。<br>&#8220;&lt;n&gt;.c&#8221;的依赖文件被自动推导为&#8220;n.y&#8221;（Yacc生成的文件），其生成命令是：&#8220;$(YACC) $(YFALGS)&#8221;。（&#8220;Yacc&#8221;是一个语法分析器，关于其细节请查看相关资料）<br><br>10、Lex C程序时的隐含规则。<br>&#8220;&lt;n&gt;.c&#8221;的依赖文件被自动推导为&#8220;n.l&#8221;（Lex生成的文件），其生成命令是：&#8220;$(LEX) $(LFALGS)&#8221;。（关于&#8220;Lex&#8221;的细节请查看相关资料）<br><br>11、Lex Ratfor程序时的隐含规则。<br>&#8220;&lt;n&gt;.r&#8221;的依赖文件被自动推导为&#8220;n.l&#8221;（Lex生成的文件），其生成命令是：&#8220;$(LEX) $(LFALGS)&#8221;。<br><br>12、从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。<br>&#8220;&lt;n&gt;.ln&#8221; （lint生成的文件）的依赖文件被自动推导为&#8220;n.c&#8221;，其生成命令是：&#8220;$(LINT) $(LINTFALGS) $(CPPFLAGS) -i&#8221;。对于&#8220;&lt;n&gt;.y&#8221;和&#8220;&lt;n&gt;.l&#8221;也是同样的规则。<br><br><br>三、隐含规则使用的变量<br><br>在隐含规则中的命令中，基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值，或是在make的命令行中传入这些值，或是在你的环境变量中设置这些值，无论怎么样，只要设置了这些特定的变量，那么其就会对隐含规则起作用。当然，你也可以利用make的&#8220;-R&#8221;或&#8220;-- no&#8211;builtin-variables&#8221;参数来取消你所定义的变量对隐含规则的作用。<br><br>例如，第一条隐含规则——编译C程序的隐含规则的命令是&#8220;$(CC) &#8211;c $(CFLAGS) $(CPPFLAGS)&#8221;。Make默认的编译命令是&#8220;cc&#8221;，如果你把变量&#8220;$(CC)&#8221;重定义成&#8220;gcc&#8221;，把变量&#8220;$(CFLAGS)&#8221;重定义成 &#8220;-g&#8221;，那么，隐含规则中的命令全部会以&#8220;gcc &#8211;c -g $(CPPFLAGS)&#8221;的样子来执行了。<br><br>我们可以把隐含规则中使用的变量分成两种：一种是命令相关的，如&#8220;CC&#8221;；一种是参数相的关，如&#8220;CFLAGS&#8221;。下面是所有隐含规则中会用到的变量：<br><br>1、关于命令的变量。<br><br>AR <br>函数库打包程序。默认命令是&#8220;ar&#8221;。 <br>AS <br>汇编语言编译程序。默认命令是&#8220;as&#8221;。<br>CC <br>C语言编译程序。默认命令是&#8220;cc&#8221;。<br>CXX <br>C++语言编译程序。默认命令是&#8220;g++&#8221;。<br>CO <br>从 RCS文件中扩展文件程序。默认命令是&#8220;co&#8221;。<br>CPP <br>C程序的预处理器（输出是标准输出设备）。默认命令是&#8220;$(CC) &#8211;E&#8221;。<br>FC <br>Fortran 和 Ratfor 的编译器和预处理程序。默认命令是&#8220;f77&#8221;。<br>GET <br>从SCCS文件中扩展文件的程序。默认命令是&#8220;get&#8221;。 <br>LEX <br>Lex方法分析器程序（针对于C或Ratfor）。默认命令是&#8220;lex&#8221;。<br>PC <br>Pascal语言编译程序。默认命令是&#8220;pc&#8221;。<br>YACC <br>Yacc文法分析器（针对于C程序）。默认命令是&#8220;yacc&#8221;。<br>YACCR <br>Yacc文法分析器（针对于Ratfor程序）。默认命令是&#8220;yacc &#8211;r&#8221;。<br>MAKEINFO <br>转换Texinfo源文件（.texi）到Info文件程序。默认命令是&#8220;makeinfo&#8221;。<br>TEX <br>从TeX源文件创建TeX DVI文件的程序。默认命令是&#8220;tex&#8221;。<br>TEXI2DVI <br>从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是&#8220;texi2dvi&#8221;。<br>WEAVE <br>转换Web到TeX的程序。默认命令是&#8220;weave&#8221;。<br>CWEAVE <br>转换C Web 到 TeX的程序。默认命令是&#8220;cweave&#8221;。<br>TANGLE <br>转换Web到Pascal语言的程序。默认命令是&#8220;tangle&#8221;。<br>CTANGLE <br>转换C Web 到 C。默认命令是&#8220;ctangle&#8221;。<br>RM <br>删除文件命令。默认命令是&#8220;rm &#8211;f&#8221;。<br><br>2、关于命令参数的变量<br><br>下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值，那么其默认值都是空。<br><br>ARFLAGS <br>函数库打包程序AR命令的参数。默认值是&#8220;rv&#8221;。<br>ASFLAGS <br>汇编语言编译器参数。（当明显地调用&#8220;.s&#8221;或&#8220;.S&#8221;文件时）。 <br>CFLAGS <br>C语言编译器参数。<br>CXXFLAGS <br>C++语言编译器参数。<br>COFLAGS <br>RCS命令参数。 <br>CPPFLAGS <br>C预处理器参数。（ C 和 Fortran 编译器也会用到）。<br>FFLAGS <br>Fortran语言编译器参数。<br>GFLAGS <br>SCCS &#8220;get&#8221;程序参数。<br>LDFLAGS <br>链接器参数。（如：&#8220;ld&#8221;）<br>LFLAGS <br>Lex文法分析器参数。<br>PFLAGS <br>Pascal语言编译器参数。<br>RFLAGS <br>Ratfor 程序的Fortran 编译器参数。<br>YFLAGS <br>Yacc文法分析器参数。 <br><br><br>四、隐含规则链<br><br>有些时候，一个目标可能被一系列的隐含规则所作用。例如，一个[.o]的文件生成，可能会是先被Yacc的[.y]文件先成[.c]，然后再被C的编译器生成。我们把这一系列的隐含规则叫做&#8220;隐含规则链&#8221;。<br><br>在上面的例子中，如果文件[.c]存在，那么就直接调用C的编译器的隐含规则，如果没有[.c]文件，但有一个[.y]文件，那么Yacc的隐含规则会被调用，生成[.c]文件，然后，再调用C编译的隐含规则最终由[.c]生成[.o]文件，达到目标。<br><br>我们把这种[.c]的文件（或是目标），叫做中间目标。不管怎么样，make会努力自动推导生成目标的一切方法，不管中间目标有多少，其都会执着地把所有的隐含规则和你书写的规则全部合起来分析，努力达到目标，所以，有些时候，可能会让你觉得奇怪，怎么我的目标会这样生成？怎么我的makefile发疯了？<br><br>在默认情况下，对于中间目标，它和一般的目标有两个地方所不同：第一个不同是除非中间的目标不存在，才会引发中间规则。第二个不同的是，只要目标成功产生，那么，产生最终目标过程中，所产生的中间目标文件会被以&#8220;rm -f&#8221;删除。<br><br>通常，一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而，你可以明显地说明一个文件或是目标是中介目标，你可以使用伪目标&#8220;.INTERMEDIATE&#8221;来强制声明。（如：.INTERMEDIATE ： mid ）<br><br>你也可以阻止make自动删除中间目标，要做到这一点，你可以使用伪目标&#8220;.SECONDARY&#8221;来强制声明（如：.SECONDARY : sec）。你还可以把你的目标，以模式的方式来指定（如：%.o）成伪目标&#8220;.PRECIOUS&#8221;的依赖目标，以保存被隐含规则所生成的中间文件。<br><br>在&#8220;隐含规则链&#8221;中，禁止同一个目标出现两次或两次以上，这样一来，就可防止在make自动推导时出现无限递归的情况。<br><br>Make 会优化一些特殊的隐含规则，而不生成中间文件。如，从文件&#8220;foo.c&#8221;生成目标程序&#8220;foo&#8221;，按道理，make会编译生成中间文件&#8220;foo.o&#8221;，然后链接成&#8220;foo&#8221;，但在实际情况下，这一动作可以被一条&#8220;cc&#8221;的命令完成（cc &#8211;o foo foo.c），于是优化过的规则就不会生成中间文件。<br><br><br><br>五、定义模式规则<br><br>你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则，只是在规则中，目标的定义需要有"%"字符。"%"的意思是表示一个或多个任意字符。在依赖目标中同样可以使用"%"，只是依赖目标中的"%"的取值，取决于其目标。<br><br>有一点需要注意的是，"%"的展开发生在变量和函数的展开之后，变量和函数的展开发生在make载入Makefile时，而模式规则中的"%"则发生在运行时。<br><br><br>1、模式规则介绍<br><br>模式规则中，至少在规则的目标定义中要包含"%"，否则，就是一般的规则。目标中的"%"定义表示对文件名的匹配，"%"表示长度任意的非空字符串。例如： "%.c"表示以".c"结尾的文件名（文件名的长度至少为3），而"s.%.c"则表示以"s."开头，".c"结尾的文件名（文件名的长度至少为 5）。<br><br>如果"%"定义在目标中，那么，目标中的"%"的值决定了依赖目标中的"%"的值，也就是说，目标中的模式的"%"决定了依赖目标中"%"的样子。例如有一个模式规则如下：<br><br>%.o : %.c ; &lt;command ......&gt;<br><br>其含义是，指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。如果要生成的目标是"a.o b.o"，那么"%c"就是"a.c b.c"。<br><br>一旦依赖目标中的"%"模式被确定，那么，make会被要求去匹配当前目录下所有的文件名，一旦找到，make就会规则下的命令，所以，在模式规则中，目标可能会是多个的，如果有模式匹配出多个目标，make就会产生所有的模式目标，此时，make关心的是依赖的文件名和生成目标的命令这两件事。<br><br><br>2、模式规则示例<br><br>下面这个例子表示了,把所有的[.c]文件都编译成[.o]文件.<br><br>%.o : %.c<br>$(CC) -c $(CFLAGS) $(CPPFLAGS) $&lt; -o $@<br><br>其中，"$@"表示所有的目标的挨个值，"$&lt;"表示了所有依赖目标的挨个值。这些奇怪的变量我们叫"自动化变量"，后面会详细讲述。<br><br>下面的这个例子中有两个目标是模式的：<br><br>%.tab.c %.tab.h: %.y<br>bison -d $&lt;<br><br>这条规则告诉make把所有的[.y]文件都以"bison -d &lt;n&gt;.y"执行，然后生成"&lt;n&gt;.tab.c"和"&lt;n&gt;.tab.h"文件。（其中，"&lt;n&gt;" 表示一个任意字符串）。如果我们的执行程序"foo"依赖于文件"parse.tab.o"和"scan.o"，并且文件"scan.o"依赖于文件 "parse.tab.h"，如果"parse.y"文件被更新了，那么根据上述的规则，"bison -d parse.y"就会被执行一次，于是，"parse.tab.o"和"scan.o"的依赖文件就齐了。（假设，"parse.tab.o"由 "parse.tab.c"生成，和"scan.o"由"scan.c"生成，而"foo"由"parse.tab.o"和"scan.o"链接生成，而且foo和其[.o]文件的依赖关系也写好，那么，所有的目标都会得到满足）<br><br><br>3、自动化变量<br><br>在上述的模式规则中，目标和依赖文件都是一系例的文件，那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标？因为在每一次的对模式规则的解析时，都会是不同的目标和依赖文件。<br><br>自动化变量就是完成这个功能的。在前面，我们已经对自动化变量有所提涉，相信你看到这里已对它有一个感性认识了。所谓自动化变量，就是这种变量会把模式中所定义的一系列的文件自动地挨个取出，直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。<br><br>下面是所有的自动化变量及其说明：<br><br>$@<br>表示规则中的目标文件集。在模式规则中，如果有多个目标，那么，"$@"就是匹配于目标中模式定义的集合。<br><br>$%<br>仅当目标是函数库文件中，表示规则中的目标成员名。例如，如果一个目标是"foo.a(bar.o)"，那么，"$%"就是"bar.o"，"$@"就是"foo.a"。如果目标不是函数库文件（Unix下是[.a]，Windows下是[.lib]），那么，其值为空。<br><br>$&lt;<br>依赖目标中的第一个目标名字。如果依赖目标是以模式（即"%"）定义的，那么"$&lt;"将是符合模式的一系列的文件集。注意，其是一个一个取出来的。<br><br>$?<br>所有比目标新的依赖目标的集合。以空格分隔。<br><br>$^<br>所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的，那个这个变量会去除重复的依赖目标，只保留一份。<br><br>$+<br>这个变量很像"$^"，也是所有依赖目标的集合。只是它不去除重复的依赖目标。<br><br>$* <br>这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b"，并且目标的模式是"a.%.b"，那么，"$*"的值就是 "dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义，那么"$*"也就不能被推导出，但是，如果目标文件的后缀是make所识别的，那么"$*"就是除了后缀的那一部分。例如：如果目标是"foo.c"，因为".c"是make所能识别的后缀名，所以，"$*" 的值就是"foo"。这个特性是GNU make的，很有可能不兼容于其它版本的make，所以，你应该尽量避免使用"$*"，除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的，那么"$*"就是空值。<br><br>当你希望只对更新过的依赖文件进行操作时，"$?"在显式规则中很有用，例如，假设有一个函数库文件叫"lib"，其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是：<br><br>lib : foo.o bar.o lose.o win.o<br>ar r lib $?<br><br>在上述所列出来的自动量变量中。四个变量（$@、$&lt;、$%、$*）在扩展时只会有一个文件，而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是在当前目录下的符合模式的文件名，只需要搭配上"D"或"F"字样。这是GNU make中老版本的特性，在新版本中，我们使用函数"dir"或"notdir"就可以做到了。"D"的含义就是Directory，就是目录，"F"的含义就是File，就是文件。<br><br>下面是对于上面的七个变量分别加上"D"或是"F"的含义：<br><br>$(@D)<br>表示"$@"的目录部分（不以斜杠作为结尾），如果"$@"值是"dir/foo.o"，那么"$(@D)"就是"dir"，而如果"$@"中没有包含斜杠的话，其值就是"."（当前目录）。<br><br>$(@F)<br>表示"$@"的文件部分，如果"$@"值是"dir/foo.o"，那么"$(@F)"就是"foo.o"，"$(@F)"相当于函数"$(notdir $@)"。<br><br>"$(*D)"<br>"$(*F)"<br>和上面所述的同理，也是取文件的目录部分和文件部分。对于上面的那个例子，"$(*D)"返回"dir"，而"$(*F)"返回"foo"<br><br>"$(%D)"<br>"$(%F)"<br>分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。<br><br>"$(&lt;D)"<br>"$(&lt;F)"<br>分别表示依赖文件的目录部分和文件部分。<br><br>"$(^D)"<br>"$(^F)"<br>分别表示所有依赖文件的目录部分和文件部分。（无相同的）<br><br>"$(+D)"<br>"$(+F)"<br>分别表示所有依赖文件的目录部分和文件部分。（可以有相同的）<br><br>"$(?D)"<br>"$(?F)"<br>分别表示被更新的依赖文件的目录部分和文件部分。<br><br>最后想提醒一下的是，对于"$&lt;"，为了避免产生不必要的麻烦，我们最好给$后面的那个特定字符都加上圆括号，比如，"$(&lt; )"就要比"$&lt;"要好一些。<br><br>还得要注意的是，这些变量只使用在规则的命令中，而且一般都是"显式规则"和"静态模式规则"（参见前面"书写规则"一章）。其在隐含规则中并没有意义。<br><br>4、模式的匹配<br><br>一般来说，一个目标的模式有一个有前缀或是后缀的"%"，或是没有前后缀，直接就是一个"%"。因为"%"代表一个或多个字符，所以在定义好了的模式中，我们把"%"所匹配的内容叫做"茎"，例如"%.c"所匹配的文件"test.c"中"test"就是"茎"。因为在目标和依赖目标中同时有"%"时，依赖目标的"茎"会传给目标，当做目标中的"茎"。<br><br>当一个模式匹配包含有斜杠（实际也不经常包含）的文件时，那么在进行模式匹配时，目录部分会首先被移开，然后进行匹配，成功后，再把目录加回去。在进行"茎"的传递时，我们需要知道这个步骤。例如有一个模式"e%t"，文件"src/eat" 匹配于该模式，于是"src/a"就是其"茎"，如果这个模式定义在依赖目标中，而被依赖于这个模式的目标中又有个模式"c%r"，那么，目标就是 "src/car"。（"茎"被传递）<br><br><br>5、重载内建隐含规则<br><br>你可以重载内建的隐含规则（或是定义一个全新的），例如你可以重新构造和内建隐含规则不同的命令，如：<br><br>%.o : %.c<br>$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)<br><br>你可以取消内建的隐含规则，只要不在后面写命令就行。如：<br><br>%.o : %.s<br><br>同样，你也可以重新定义一个全新的隐含规则，其在隐含规则中的位置取决于你在哪里写下这个规则。朝前的位置就靠前。<br><br><br>六、老式风格的"后缀规则"<br><br>后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步地取代。因为模式规则更强更清晰。为了和老版本的Makefile兼容，GNU make同样兼容于这些东西。后缀规则有两种方式："双后缀"和"单后缀"。<br><br>双后缀规则定义了一对后缀：目标文件的后缀和依赖目标（源文件）的后缀。如".c.o"相当于"%o : %c"。单后缀规则只定义一个后缀，也就是源文件的后缀。如".c"相当于"% : %.c"。<br><br>后缀规则中所定义的后缀应该是make所认识的，如果一个后缀是make所认识的，那么这个规则就是单后缀规则，而如果两个连在一起的后缀都被make所认识，那就是双后缀规则。例如：".c"和".o"都是make所知道。因而，如果你定义了一个规则是".c.o"那么其就是双后缀规则，意义就是".c" 是源文件的后缀，".o"是目标文件的后缀。如下示例：<br><br>.c.o:<br>$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $&lt;<br><br>后缀规则不允许任何的依赖文件，如果有依赖文件的话，那就不是后缀规则，那些后缀统统被认为是文件名，如：<br><br>.c.o: foo.h<br>$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $&lt;<br><br>这个例子，就是说，文件".c.o"依赖于文件"foo.h"，而不是我们想要的这样：<br><br>%.o: %.c foo.h<br>$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $&lt;<br><br>后缀规则中，如果没有命令，那是毫无意义的。因为他也不会移去内建的隐含规则。<br><br>而要让make知道一些特定的后缀，我们可以使用伪目标".SUFFIXES"来定义或是删除，如：<br><br>.SUFFIXES: .hack .win<br><br>把后缀.hack和.win加入后缀列表中的末尾。<br><br>.SUFFIXES: # 删除默认的后缀<br>.SUFFIXES: .c .o .h # 定义自己的后缀<br><br>先清楚默认后缀，后定义自己的后缀列表。<br><br>make的参数"-r"或"-no-builtin-rules"也会使用得默认的后缀列表为空。而变量"SUFFIXE"被用来定义默认的后缀列表，你可以用".SUFFIXES"来改变后缀列表，但请不要改变变量"SUFFIXE"的值。<br><br><br>七、隐含规则搜索算法<br><br>比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。请注意，在下面，我们没有提到后缀规则，原因是，所有的后缀规则在Makefile被载入内存时，会被转换成模式规则。如果目标是"archive(member)"的函数库文件模式，那么这个算法会被运行两次，第一次是找目标T，如果没有找到的话，那么进入第二次，第二次会把"member"当作T来搜索。<br><br>1、把T的目录部分分离出来。叫D，而剩余部分叫N。（如：如果T是"src/foo.o"，那么，D就是"src/"，N就是"foo.o"）<br><br>2、创建所有匹配于T或是N的模式规则列表。<br><br>3、如果在模式规则列表中有匹配所有文件的模式，如"%"，那么从列表中移除其它的模式。<br><br>4、移除列表中没有命令的规则。<br><br>5、对于第一个在列表中的模式规则：<br>1）推导其"茎"S，S应该是T或是N匹配于模式中"%"非空的部分。<br>2）计算依赖文件。把依赖文件中的"%"都替换成"茎"S。如果目标模式中没有包含斜框字符，而把D加在第一个依赖文件的开头。<br>3）测试是否所有的依赖文件都存在或是理当存在。（如果有一个文件被定义成另外一个规则的目标文件，或者是一个显式规则的依赖文件，那么这个文件就叫"理当存在"）<br>4）如果所有的依赖文件存在或是理当存在，或是就没有依赖文件。那么这条规则将被采用，退出该算法。<br><br>6、如果经过第5步，没有模式规则被找到，那么就做更进一步的搜索。对于存在于列表中的第一个模式规则：<br>1）如果规则是终止规则，那就忽略它，继续下一条模式规则。<br>2）计算依赖文件。（同第5步）<br>3）测试所有的依赖文件是否存在或是理当存在。<br>4）对于不存在的依赖文件，递归调用这个算法查找他是否可以被隐含规则找到。<br>5）如果所有的依赖文件存在或是理当存在，或是就根本没有依赖文件。那么这条规则被采用，退出该算法。<br><br>7、如果没有隐含规则可以使用，查看".DEFAULT"规则，如果有，采用，把".DEFAULT"的命令给T使用。<br><br>一旦规则被找到，就会执行其相当的命令，而此时，我们的自动化变量的值才会生成。<br><br><br>使用make更新函数库文件<br>———————————<br><br>函数库文件也就是对Object文件（程序编译的中间文件）的打包文件。在Unix下，一般是由命令"ar"来完成打包工作。<br><br>一、函数库文件的成员<br><br>一个函数库文件由多个文件组成。你可以以如下格式指定函数库文件及其组成：<br><br>archive(member)<br><br>这个不是一个命令，而一个目标和依赖的定义。一般来说，这种用法基本上就是为了"ar"命令来服务的。如：<br><br>foolib(hack.o) : hack.o<br>ar cr foolib hack.o<br><br>如果要指定多个member，那就以空格分开，如：<br><br>foolib(hack.o kludge.o)<br><br>其等价于：<br><br>foolib(hack.o) foolib(kludge.o)<br><br>你还可以使用Shell的文件通配符来定义，如：<br><br>foolib(*.o)<br><br><br>二、函数库成员的隐含规则<br><br>当make 搜索一个目标的隐含规则时，一个特殊的特性是，如果这个目标是"a(m)"形式的，其会把目标变成"(m)"。于是，如果我们的成员是"%.o"的模式定义，并且如果我们使用"make foo.a(bar.o)"的形式调用Makefile时，隐含规则会去找"bar.o"的规则，如果没有定义bar.o的规则，那么内建隐含规则生效， make会去找bar.c文件来生成bar.o，如果找得到的话，make执行的命令大致如下：<br><br>cc -c bar.c -o bar.o<br>ar r foo.a bar.o<br>rm -f bar.o<br><br>还有一个变量要注意的是"$%"，这是专属函数库文件的自动化变量，有关其说明请参见"自动化变量"一节。<br><br><br>三、函数库文件的后缀规则<br><br>你可以使用"后缀规则"和"隐含规则"来生成函数库打包文件，如：<br><br>.c.a:<br>$(CC) $(CFLAGS) $(CPPFLAGS) -c $&lt; -o $*.o<br>$(AR) r $@ $*.o<br>$(RM) $*.o<br><br>其等效于：<br><br>(%.o) : %.c<br>$(CC) $(CFLAGS) $(CPPFLAGS) -c $&lt; -o $*.o<br>$(AR) r $@ $*.o<br>$(RM) $*.o<br><br><br>四、注意事项<br><br>在进行函数库打包文件生成时，请小心使用make的并行机制（"-j"参数）。如果多个ar命令在同一时间运行在同一个函数库打包文件上，就很有可以损坏这个函数库文件。所以，在make未来的版本中，应该提供一种机制来避免并行操作发生在函数打包文件上。<br><br>但就目前而言，你还是应该不要尽量不要使用"-j"参数。<br><br><br><br>后序<br>——<br><br>终于到写结束语的时候了，以上基本上就是GNU make的Makefile的所有细节了。其它的产商的make基本上也就是这样的，无论什么样的make，都是以文件的依赖性为基础的，其基本是都是遵循一个标准的。这篇文档中80%的技术细节都适用于任何的make，我猜测"函数"那一章的内容可能不是其它make所支持的，而隐含规则方面，我想不同的make会有不同的实现，我没有精力来查看GNU的make和VC的nmake、BCB的make，或是别的UNIX下的make有些什么样的差别，一是时间精力不够，二是因为我基本上都是在Unix下使用make，以前在SCO Unix和IBM的AIX，现在在Linux、Solaris、HP-UX、AIX和Alpha下使用，Linux和Solaris下更多一点。不过，我可以肯定的是，在Unix下的make，无论是哪种平台，几乎都使用了Richard Stallman开发的make和cc/gcc的编译器，而且，基本上都是GNU的make（公司里所有的UNIX机器上都被装上了GNU的东西，所以，使用GNU的程序也就多了一些）。GNU的东西还是很不错的，特别是使用得深了以后，越来越觉得GNU的软件的强大，也越来越觉得GNU的在操作系统中（主要是Unix，甚至Windows）"杀伤力"。<br><br>对于上述所有的make的细节，我们不但可以利用make这个工具来编译我们的程序，还可以利用make来完成其它的工作，因为规则中的命令可以是任何Shell之下的命令，所以，在Unix下，你不一定只是使用程序语言的编译器，你还可以在Makefile中书写其它的命令，如：tar、awk、mail、sed、cvs、compress、ls、rm、yacc、rpm、 ftp&#8230;&#8230;等等，等等，来完成诸如"程序打包"、"程序备份"、"制作程序安装包"、"提交代码"、"使用程序模板"、"合并文件"等等五花八门的功能，文件操作，文件管理，编程开发设计，或是其它一些异想天开的东西。比如，以前在书写银行交易程序时，由于银行的交易程序基本一样，就见到有人书写了一些交易的通用程序模板，在该模板中把一些网络通讯、数据库操作的、业务操作共性的东西写在一个文件中，在这些文件中用些诸如"@@@N、###N"奇怪字串标注一些位置，然后书写交易时，只需按照一种特定的规则书写特定的处理，最后在make时，使用awk和sed，把模板中的"@@@N、###N"等字串替代成特定的程序，形成C文件，然后再编译。这个动作很像数据库的"扩展C"语言（即在C语言中用"EXEC　SQL"的样子执行SQL语句，在用 cc/gcc编译之前，需要使用"扩展C"的翻译程序，如cpre，把其翻译成标准C）。如果你在使用make时有一些更为绝妙的方法，请记得告诉我啊。<br><br>回头看看整篇文档，不觉记起几年前刚刚开始在Unix下做开发的时候，有人问我会不会写Makefile时，我两眼发直，根本不知道在说什么。一开始看到别人在vi中写完程序后输入"!make"时，还以为是vi的功能，后来才知道有一个Makefile在作怪，于是上网查啊查，那时又不愿意看英文，发现就根本没有中文的文档介绍Makefile，只得看别人写的Makefile，自己瞎碰瞎搞才积累了一点知识，但在很多地方完全是知其然不知所以然。后来开始从事UNIX下产品软件的开发，看到一个400人年，近200万行代码的大工程，发现要编译这样一个庞然大物，如果没有Makefile，那会是多么恐怖的一样事啊。于是横下心来，狠命地读了一堆英文文档，才觉得对其掌握了。但发现目前网上对Makefile介绍的文章还是少得那么的可怜，所以想写这样一篇文章，共享给大家，希望能对各位有所帮助。<br><br>现在我终于写完了，看了看文件的创建时间，这篇技术文档也写了两个多月了。发现，自己知道是一回事，要写下来，跟别人讲述又是另外一回事，而且，现在越来越没有时间专研技术细节，所以在写作时，发现在阐述一些细节问题时很难做到严谨和精练，而且对先讲什么后讲什么不是很清楚，所以，还是参考了一些国外站点上的资料和题纲，以及一些技术书籍的语言风格，才得以完成。整篇文档的提纲是基于GNU的 Makefile技术手册的提纲来书写的，并结合了自己的工作经验，以及自己的学习历程。因为从来没有写过这么长，这么细的文档，所以一定会有很多地方存在表达问题，语言歧义或是错误。因些，我迫切地得等待各位给我指证和建议，以及任何的反馈。<br><br>最后，还是利用这个后序，介绍一下自己。我目前从事于所有Unix平台下的软件研发，主要是做分布式计算/网格计算方面的系统产品软件，并且我对于下一代的计算机革命——网格计算非常地感兴趣，对于分布式计算、P2P、Web Service、J2EE技术方向也很感兴趣，同时，对于项目实施、团队管理、项目管理也小有心得，希望同样和我战斗在&#8220;技术和管理并重&#8221;的阵线上的年轻一代，能够和我多多地交流。我的MSN是：haoel@hotmail.com（常用），QQ是：753640（不常用）。（注：请勿给我MSN的邮箱发信，由于hotmail的垃圾邮件导致我拒收这个邮箱的所有来信）<br><br>我欢迎任何形式的交流，无论是讨论技术还是管理，或是其它海阔天空的东西。除了政治和娱乐新闻我不关心，其它只要积极向上的东西我都欢迎！<br><br>最最后，我还想介绍一下make程序的设计开发者。<br><br>首当其冲的是： Richard Stallman <br><br>开源软件的领袖和先驱，从来没有领过一天工资，从来没有使用过Windows操作系统。对于他的事迹和他的软件以及他的思想，我无需说过多的话，相信大家对这个人并不比我陌生，这是他的主页：</font><a href="http://www.stallman.org/" target=_blank><font size=2>http://www.stallman.org/</font> <font size=2>。<br><br><br><br>第二位是：Roland McGrath <br><br>个人主页是：</font></a><a href="http://www.frob.com/%7Eroland/" target=_blank><font size=2>http://www.frob.com/~roland/</font></a><font size=2> ，下面是他的一些事迹：<br><br>1） 合作编写了并维护GNU make。<br><br>2） 和Thomas Bushnell一同编写了GNU Hurd。<br><br>3） 编写并维护着GNU C library。 <br><br>4） 合作编写并维护着部分的GNU Emacs。 <br><br><br><br>在此，向这两位开源项目的斗士致以最真切的敬意。<br><br><br>（全文完）</font></font></font></font></font></font></p>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59815.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-07-03 16:52 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59815.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个很好的makefile教程3（转载）</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59814.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Fri, 03 Jul 2009 08:51:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59814.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59814.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59814.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59814.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59814.html</trackback:ping><description><![CDATA[<span id=ArticleContent1_ArticleContent1_lblContent>&nbsp;
<p><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica" size=2>使用函数<br>————<br>在Makefile中可以使用函数来处理变量，从而让我们的命令或是规则更为的灵活和具有智能。make所支持的函数也不算很多，不过已经足够我们的操作了。函数调用后，函数的返回值可以当做变量来使用。<br><br><br>一、函数的调用语法<br><br>函数调用，很像变量的使用，也是以&#8220;$&#8221;来标识的，其语法如下：<br><br>$(&lt;function&gt; &lt;arguments&gt; )<br><br>或是<br><br>${&lt;function&gt; &lt;arguments&gt;}<br><br>这里，&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;的形式。因为统一会更清楚，也会减少一些不必要的麻烦。<br><br>还是来看一个示例：<br><br>comma:= ,<br>empty:=<br>space:= $(empty) $(empty)<br>foo:= a b c<br>bar:= $(subst $(space),$(comma),$(foo))<br><br>在这个示例中，$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格，$(foo)的值是&#8220;a b c&#8221;，$(bar)的定义用，调用了函数&#8220;subst&#8221;，这是一个替换函数，这个函数有三个参数，第一个参数是被替换字串，第二个参数是替换字串，第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号，所以$(bar)的值是&#8220;a,b,c&#8221;。<br><br><br>二、字符串处理函数<br><br>$(subst &lt;from&gt;,&lt;to&gt;,&lt;text&gt; ) <br><br>名称：字符串替换函数——subst。<br>功能：把字串&lt;text&gt;中的&lt;from&gt;字符串替换成&lt;to&gt;。<br>返回：函数返回被替换过后的字符串。<br><br>示例：<br><br>$(subst ee,EE,feet on the street)，<br><br>把&#8220;feet on the street&#8221;中的&#8220;ee&#8221;替换成&#8220;EE&#8221;，返回结果是&#8220;fEEt on the strEEt&#8221;。<br><br><br>$(patsubst &lt;pattern&gt;,&lt;replacement&gt;,&lt;text&gt; ) <br><br>名称：模式字符串替换函数——patsubst。<br>功能：查找&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>返回：函数返回被替换过后的字符串。<br><br>示例：<br><br>$(patsubst %.c,%.o,x.c.c bar.c)<br><br>把字串&#8220;x.c.c bar.c&#8221;符合模式[%.c]的单词替换成[%.o]，返回结果是&#8220;x.c.o bar.o&#8221;<br><br>备注：<br><br>这和我们前面&#8220;变量章节&#8221;说过的相关知识有点相似。如：<br><br>&#8220;$(var:&lt;pattern&gt;=&lt;replacement&gt; )&#8221;<br>相当于<br>&#8220;$(patsubst &lt;pattern&gt;,&lt;replacement&gt;,$(var))&#8221;，<br><br>而&#8220;$(var: &lt;suffix&gt;=&lt;replacement&gt; )&#8221;<br>则相当于<br>&#8220;$(patsubst %&lt;suffix&gt;,%&lt;replacement&gt;,$(var))&#8221;。<br><br>例如有：objects = foo.o bar.o baz.o，<br>那么，&#8220;$(objects:.o=.c)&#8221;和&#8220;$(patsubst %.o,%.c,$(objects))&#8221;是一样的。<br><br>$(strip &lt;string&gt; )<br><br>名称：去空格函数——strip。<br>功能：去掉&lt;string&gt;字串中开头和结尾的空字符。<br>返回：返回被去掉空格的字符串值。<br>示例：<br><br>$(strip a b c )<br><br>把字串&#8220;a b c &#8221;去到开头和结尾的空格，结果是&#8220;a b c&#8221;。<br><br>$(findstring &lt;find&gt;,&lt;in&gt; )<br><br>名称：查找字符串函数——findstring。<br>功能：在字串&lt;in&gt;中查找&lt;find&gt;字串。<br>返回：如果找到，那么返回&lt;find&gt;，否则返回空字符串。<br>示例：<br><br>$(findstring a,a b c)<br>$(findstring a,b c)<br><br>第一个函数返回&#8220;a&#8221;字符串，第二个返回&#8220;&#8221;字符串（空字符串）<br><br>$(filter &lt;pattern...&gt;,&lt;text&gt; )<br><br>名称：过滤函数——filter。<br>功能：以&lt;pattern&gt;模式过滤&lt;text&gt;字符串中的单词，保留符合模式&lt;pattern&gt;的单词。可以有多个模式。<br>返回：返回符合模式&lt;pattern&gt;的字串。<br>示例：<br><br>sources := foo.c bar.c baz.s ugh.h<br>foo: $(sources)<br>cc $(filter %.c %.s,$(sources)) -o foo<br><br>$(filter %.c %.s,$(sources))返回的值是&#8220;foo.c bar.c baz.s&#8221;。<br><br>$(filter-out &lt;pattern...&gt;,&lt;text&gt; )<br><br>名称：反过滤函数——filter-out。<br>功能：以&lt;pattern&gt;模式过滤&lt;text&gt;字符串中的单词，去除符合模式&lt;pattern&gt;的单词。可以有多个模式。<br>返回：返回不符合模式&lt;pattern&gt;的字串。<br>示例：<br><br>objects=main1.o foo.o main2.o bar.o<br>mains=main1.o main2.o<br><br>$(filter-out $(mains),$(objects)) 返回值是&#8220;foo.o bar.o&#8221;。<br><br>$(sort &lt;list&gt; )<br><br>名称：排序函数——sort。<br>功能：给字符串&lt;list&gt;中的单词排序（升序）。<br>返回：返回排序后的字符串。<br>示例：$(sort foo bar lose)返回&#8220;bar foo lose&#8221; 。<br>备注：sort函数会去掉&lt;list&gt;中相同的单词。<br><br>$(word &lt;n&gt;,&lt;text&gt; )<br><br>名称：取单词函数——word。<br>功能：取字符串&lt;text&gt;中第&lt;n&gt;个单词。（从一开始）<br>返回：返回字符串&lt;text&gt;中第&lt;n&gt;个单词。如果&lt;n&gt;比&lt;text&gt;中的单词数要大，那么返回空字符串。<br>示例：$(word 2, foo bar baz)返回值是&#8220;bar&#8221;。<br><br>$(wordlist &lt;s&gt;,&lt;e&gt;,&lt;text&gt; ) <br><br>名称：取单词串函数——wordlist。<br>功能：从字符串&lt;text&gt;中取从&lt;s&gt;开始到&lt;e&gt;的单词串。&lt;s&gt;和&lt;e&gt;是一个数字。<br>返回：返回字符串&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>示例： $(wordlist 2, 3, foo bar baz)返回值是&#8220;bar baz&#8221;。<br><br>$(words &lt;text&gt; )<br><br>名称：单词个数统计函数——words。<br>功能：统计&lt;text&gt;中字符串中的单词个数。<br>返回：返回&lt;text&gt;中的单词数。<br>示例：$(words, foo bar baz)返回值是&#8220;3&#8221;。<br>备注：如果我们要取&lt;text&gt;中最后的一个单词，我们可以这样：$(word $(words &lt;text&gt; ),&lt;text&gt; )。<br><br>$(firstword &lt;text&gt; )<br><br>名称：首单词函数——firstword。<br>功能：取字符串&lt;text&gt;中的第一个单词。<br>返回：返回字符串&lt;text&gt;的第一个单词。<br>示例：$(firstword foo bar)返回值是&#8220;foo&#8221;。<br>备注：这个函数可以用word函数来实现：$(word 1,&lt;text&gt; )。<br><br>以上，是所有的字符串操作函数，如果搭配混合使用，可以完成比较复杂的功能。这里，举一个现实中应用的例子。我们知道，make使用&#8220;VPATH&#8221;变量来指定&#8220;依赖文件&#8221;的搜索路径。于是，我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数CFLAGS，如：<br><br>override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))<br><br>如果我们的&#8220;$(VPATH)&#8221;值是&#8220;src:../headers&#8221;，那么&#8220;$(patsubst %,-I%,$(subst :, ,$(VPATH)))&#8221;将返回&#8220;-Isrc -I../headers&#8221;，这正是cc或gcc搜索头文件路径的参数。<br><br><br>三、文件名操作函数<br><br>下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。<br><br>$(dir &lt;names...&gt; ) <br><br>名称：取目录函数——dir。<br>功能：从文件名序列&lt;names&gt;中取出目录部分。目录部分是指最后一个反斜杠（&#8220;/&#8221;）之前的部分。如果没有反斜杠，那么返回&#8220;./&#8221;。<br>返回：返回文件名序列&lt;names&gt;的目录部分。<br>示例： $(dir src/foo.c hacks)返回值是&#8220;src/ ./&#8221;。<br><br>$(notdir &lt;names...&gt; ) <br><br>名称：取文件函数——notdir。<br>功能：从文件名序列&lt;names&gt;中取出非目录部分。非目录部分是指最后一个反斜杠（&#8220;/&#8221;）之后的部分。<br>返回：返回文件名序列&lt;names&gt;的非目录部分。<br>示例： $(notdir src/foo.c hacks)返回值是&#8220;foo.c hacks&#8221;。<br><br>$(suffix &lt;names...&gt; ) <br><br>名称：取后缀函数——suffix。<br>功能：从文件名序列&lt;names&gt;中取出各个文件名的后缀。<br>返回：返回文件名序列&lt;names&gt;的后缀序列，如果文件没有后缀，则返回空字串。<br>示例：$(suffix src/foo.c src-1.0/bar.c hacks)返回值是&#8220;.c .c&#8221;。<br><br>$(basename &lt;names...&gt; )<br><br>名称：取前缀函数——basename。<br>功能：从文件名序列&lt;names&gt;中取出各个文件名的前缀部分。<br>返回：返回文件名序列&lt;names&gt;的前缀序列，如果文件没有前缀，则返回空字串。<br>示例：$(basename src/foo.c src-1.0/bar.c hacks)返回值是&#8220;src/foo src-1.0/bar hacks&#8221;。<br><br>$(addsuffix &lt;suffix&gt;,&lt;names...&gt; ) <br><br>名称：加后缀函数——addsuffix。<br>功能：把后缀&lt;suffix&gt;加到&lt;names&gt;中的每个单词后面。<br>返回：返回加过后缀的文件名序列。<br>示例：$(addsuffix .c,foo bar)返回值是&#8220;foo.c bar.c&#8221;。<br><br>$(addprefix &lt;prefix&gt;,&lt;names...&gt; ) <br><br>名称：加前缀函数——addprefix。<br>功能：把前缀&lt;prefix&gt;加到&lt;names&gt;中的每个单词后面。<br>返回：返回加过前缀的文件名序列。<br>示例：$(addprefix src/,foo bar)返回值是&#8220;src/foo src/bar&#8221;。<br><br>$(join &lt;list1&gt;,&lt;list2&gt; )<br><br>名称：连接函数——join。<br>功能：把&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>返回：返回连接过后的字符串。<br>示例：$(join aaa bbb , 111 222 333)返回值是&#8220;aaa111 bbb222 333&#8221;。<br><br><br><br>四、foreach 函数<br><br><br>foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的，Makefile中的foreach函数几乎是仿照于Unix标准Shell （/bin/sh）中的for语句，或是C-Shell（/bin/csh）中的foreach语句而构建的。它的语法是：<br><br><br><br>$(foreach &lt;var&gt;,&lt;list&gt;,&lt;text&gt; )<br><br><br><br>这个函数的意思是，把参数&lt;list&gt;中的单词逐一取出放到参数&lt;var&gt;所指定的变量中，然后再执行&lt;text&gt;所包含的表达式。每一次&lt;text&gt;会返回一个字符串，循环过程中，&lt;text&gt;的所返回的每个字符串会以空格分隔，最后当整个循环结束时，&lt;text&gt;所返回的每个字符串所组成的整个字符串（以空格分隔）将会是foreach函数的返回值。<br><br><br><br>所以，&lt;var&gt;最好是一个变量名，&lt;list&gt;可以是一个表达式，而&lt;text&gt;中一般会使用&lt;var&gt;这个参数来依次枚举&lt;list&gt;中的单词。举个例子：<br><br><br><br>names := a b c d<br><br>files := $(foreach n,$(names),$(n).o)<br><br><br><br>上面的例子中，$(name)中的单词会被挨个取出，并存到变量&#8220;n&#8221;中，&#8220;$(n).o&#8221;每次根据&#8220;$(n)&#8221;计算出一个值，这些值以空格分隔，最后作为foreach函数的返回，所以，$(files)的值是&#8220;a.o b.o c.o d.o&#8221;。<br><br><br><br>注意，foreach中的&lt;var&gt;参数是一个临时的局部变量，foreach函数执行完后，参数&lt;var&gt;的变量将不在作用，其作用域只在foreach函数当中。<br><br><br><br><br><br>五、if 函数<br><br><br>if函数很像GNU的make所支持的条件语句——ifeq（参见前面所述的章节），if函数的语法是：<br><br><br><br>$(if &lt;condition&gt;,&lt;then-part&gt; ) <br><br><br><br>或是<br><br><br><br>$(if &lt;condition&gt;,&lt;then-part&gt;,&lt;else-part&gt; )<br><br><br><br>可见，if函数可以包含&#8220;else&#8221;部分，或是不含。即if函数的参数可以是两个，也可以是三个。&lt;condition&gt;参数是if的表达式，如果其返回的为非空字符串，那么这个表达式就相当于返回真，于是，&lt;then-part&gt;会被计算，否则&lt;else-part&gt; 会被计算。<br><br><br><br>而if函数的返回值是，如果&lt;condition&gt;为真（非空字符串），那个&lt;then- part&gt;会是整个函数的返回值，如果&lt;condition&gt;为假（空字符串），那么&lt;else-part&gt;会是整个函数的返回值，此时如果&lt;else-part&gt;没有被定义，那么，整个函数返回空字串。<br><br><br><br>所以，&lt;then-part&gt;和&lt;else-part&gt;只会有一个被计算。<br><br><br><br><br><br>六、call函数<br><br><br>call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式，这个表达式中，你可以定义许多参数，然后你可以用call函数来向这个表达式传递参数。其语法是：<br><br><br><br>$(call &lt;expression&gt;,&lt;parm1&gt;,&lt;parm2&gt;,&lt;parm3&gt;...)<br><br><br><br>当make 执行这个函数时，&lt;expression&gt;参数中的变量，如$(1)，$(2)，$(3)等，会被参数&lt;parm1&gt;，&lt; parm2&gt;，&lt;parm3&gt;依次取代。而&lt;expression&gt;的返回值就是call函数的返回值。例如：<br><br>reverse = $(1) $(2)<br><br>foo = $(call reverse,a,b)<br><br><br><br>那么，foo的值就是&#8220;a b&#8221;。当然，参数的次序是可以自定义的，不一定是顺序的，如：<br><br><br><br>reverse = $(2) $(1)<br><br>foo = $(call reverse,a,b)<br><br><br><br>此时的foo的值就是&#8220;b a&#8221;。<br><br><br><br><br><br>七、origin函数<br>origin函数不像其它的函数，他并不操作变量的值，他只是告诉你你的这个变量是哪里来的？其语法是：<br><br><br><br>$(origin &lt;variable&gt; )<br><br><br><br>注意，&lt;variable&gt;是变量的名字，不应该是引用。所以你最好不要在&lt;variable&gt;中使用&#8220;$&#8221;字符。Origin函数会以其返回值来告诉你这个变量的&#8220;出生情况&#8221;，下面，是origin函数的返回值:<br><br><br><br>&#8220;undefined&#8221;<br><br>如果&lt;variable&gt;从来没有定义过，origin函数返回这个值&#8220;undefined&#8221;。<br><br><br><br>&#8220;default&#8221;<br><br>如果&lt;variable&gt;是一个默认的定义，比如&#8220;CC&#8221;这个变量，这种变量我们将在后面讲述。<br><br><br><br>&#8220;environment&#8221;<br><br>如果&lt;variable&gt;是一个环境变量，并且当Makefile被执行时，&#8220;-e&#8221;参数没有被打开。<br><br><br><br>&#8220;file&#8221;<br><br>如果&lt;variable&gt;这个变量被定义在Makefile中。<br><br><br><br>&#8220;command line&#8221;<br><br>如果&lt;variable&gt;这个变量是被命令行定义的。<br><br><br><br>&#8220;override&#8221;<br><br>如果&lt;variable&gt;是被override指示符重新定义的。<br><br><br><br>&#8220;automatic&#8221;<br><br>如果&lt;variable&gt;是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。<br><br><br><br>这些信息对于我们编写Makefile是非常有用的，例如，假设我们有一个Makefile其包了一个定义文件Make.def，在Make.def中定义了一个变量&#8220;bletch&#8221;，而我们的环境中也有一个环境变量&#8220;bletch&#8221;，此时，我们想判断一下，如果变量来源于环境，那么我们就把之重定义了，如果来源于Make.def或是命令行等非环境的，那么我们就不重新定义它。于是，在我们的Makefile中，我们可以这样写：<br><br><br><br>ifdef bletch<br><br>ifeq "$(origin bletch)" "environment"<br><br>bletch = barf, gag, etc.<br><br>endif<br><br>endif<br><br><br><br>当然，你也许会说，使用override关键字不就可以重新定义环境中的变量了吗？为什么需要使用这样的步骤？是的，我们用override是可以达到这样的效果，可是override过于粗暴，它同时会把从命令行定义的变量也覆盖了，而我们只想重新定义环境传来的，而不想重新定义命令行传来的。<br><br><br><br><br><br>八、shell函数<br><br><br>shell 函数也不像其它的函数。顾名思义，它的参数应该就是操作系统Shell的命令。它和反引号&#8220;`&#8221;是相同的功能。这就是说，shell函数把执行操作系统命令后的输出作为函数返回。于是，我们可以用操作系统命令以及字符串处理命令awk，sed等等命令来生成一个变量，如：<br><br><br><br>contents := $(shell cat foo)<br><br><br><br>files := $(shell echo *.c)<br><br><br><br>注意，这个函数会新生成一个Shell程序来执行命令，所以你要注意其运行性能，如果你的Makefile中有一些比较复杂的规则，并大量使用了这个函数，那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。<br><br><br><br><br><br>九、控制make的函数<br><br><br>make提供了一些函数来控制make的运行。通常，你需要检测一些运行Makefile时的运行时信息，并且根据这些信息来决定，你是让make继续执行，还是停止。<br><br><br><br>$(error &lt;text ...&gt; )<br><br><br><br>产生一个致命的错误，&lt;text ...&gt;是错误信息。注意，error函数不会在一被使用就会产生错误信息，所以如果你把其定义在某个变量中，并在后续的脚本中使用这个变量，那么也是可以的。例如：<br><br><br><br>示例一：<br><br>ifdef ERROR_001<br><br>$(error error is $(ERROR_001))<br><br>endif<br><br><br><br>示例二：<br><br>ERR = $(error found an error!)<br><br>.PHONY: err<br><br>err: ; $(ERR)<br><br><br><br>示例一会在变量ERROR_001定义了后执行时产生error调用，而示例二则在目录err被执行时才发生error调用。<br><br><br><br>$(warning &lt;text ...&gt; )<br><br><br><br>这个函数很像error函数，只是它并不会让make退出，只是输出一段警告信息，而make继续执行。<br><br>make 的运行<br>——————<br><br>一般来说，最简单的就是直接在命令行下输入make命令，make命令会找当前目录的makefile来执行，一切都是自动的。但也有时你也许只想让 make重编译某些文件，而不是整个工程，而又有的时候你有几套编译规则，你想在不同的时候使用不同的编译规则，等等。本章节就是讲述如何使用make命令的。<br><br>一、make的退出码<br><br>make命令执行后有三个退出码：<br><br>0 —— 表示成功执行。<br>1 —— 如果make运行时出现任何错误，其返回1。<br>2 —— 如果你使用了make的&#8220;-q&#8221;选项，并且make使得一些目标不需要更新，那么返回2。<br><br>Make的相关参数我们会在后续章节中讲述。<br><br><br>二、指定Makefile<br><br>前面我们说过，GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——&#8220;GNUmakefile&#8221;、&#8220;makefile&#8221;和&#8220;Makefile&#8221;。其按顺序找这三个文件，一旦找到，就开始读取这个文件并执行。<br><br>当前，我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能，我们要使用make的&#8220;-f&#8221;或是&#8220;--file&#8221;参数（&#8220;-- makefile&#8221;参数也行）。例如，我们有个makefile的名字是&#8220;hchen.mk&#8221;，那么，我们可以这样来让make来执行这个文件：<br><br>make &#8211;f hchen.mk<br><br>如果在make的命令行是，你不只一次地使用了&#8220;-f&#8221;参数，那么，所有指定的makefile将会被连在一起传递给make执行。<br><br><br>三、指定目标<br><br>一般来说，make的最终目标是makefile中的第一个目标，而其它目标一般是由这个目标连带出来的。这是make的默认行为。当然，一般来说，你的 makefile中的第一个目标是由许多个目标组成，你可以指示make，让其完成你所指定的目标。要达到这一目的很简单，需在make命令后直接跟目标的名字就可以完成（如前面提到的&#8220;make clean&#8221;形式）<br><br>任何在makefile中的目标都可以被指定成终极目标，但是除了以 &#8220;-&#8221;打头，或是包含了&#8220;=&#8221;的目标，因为有这些字符的目标，会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标，也就是说，只要make可以找到其隐含规则推导规则，那么这个隐含目标同样可以被指定成终极目标。<br><br>有一个make的环境变量叫&#8220;MAKECMDGOALS&#8221;，这个变量中会存放你所指定的终极目标的列表，如果在命令行上，你没有指定目标，那么，这个变量是空值。这个变量可以让你使用在一些比较特殊的情形下。比如下面的例子：<br><br>sources = foo.c bar.c<br>ifneq ( $(MAKECMDGOALS),clean)<br>include $(sources:.c=.d)<br>endif<br><br>基于上面的这个例子，只要我们输入的命令不是&#8220;make clean&#8221;，那么makefile会自动包含&#8220;foo.d&#8221;和&#8220;bar.d&#8221;这两个makefile。<br><br>使用指定终极目标的方法可以很方便地让我们编译我们的程序，例如下面这个例子：<br><br>.PHONY: all<br>all: prog1 prog2 prog3 prog4<br><br>从这个例子中，我们可以看到，这个makefile中有四个需要编译的程序——&#8220;prog1&#8221;， &#8220;prog2&#8221;， &#8220;prog3&#8221;和 &#8220;prog4&#8221;，我们可以使用&#8220;make all&#8221;命令来编译所有的目标（如果把all置成第一个目标，那么只需执行&#8220;make&#8221;），我们也可以使用&#8220;make prog2&#8221;来单独编译目标&#8220;prog2&#8221;。<br><br>即然make可以指定所有makefile中的目标，那么也包括&#8220;伪目标&#8221;，于是我们可以根据这种性质来让我们的makefile根据指定的不同的目标来完成不同的事。在Unix世界中，软件发布时，特别是GNU这种开源软件的发布时，其 makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们的makefile中的目标。<br><br>&#8220;all&#8221;<br>这个伪目标是所有目标的目标，其功能一般是编译所有的目标。<br>&#8220;clean&#8221;<br>这个伪目标功能是删除所有被make创建的文件。<br>&#8220;install&#8221;<br>这个伪目标功能是安装已编译好的程序，其实就是把目标执行文件拷贝到指定的目标中去。<br>&#8220;print&#8221;<br>这个伪目标的功能是例出改变过的源文件。<br>&#8220;tar&#8221;<br>这个伪目标功能是把源程序打包备份。也就是一个tar文件。<br>&#8220;dist&#8221;<br>这个伪目标功能是创建一个压缩文件，一般是把tar文件压成Z文件。或是gz文件。<br>&#8220;TAGS&#8221;<br>这个伪目标功能是更新所有的目标，以备完整地重编译使用。<br>&#8220;check&#8221;和&#8220;test&#8221;<br>这两个伪目标一般用来测试makefile的流程。<br><br>当然一个项目的makefile中也不一定要书写这样的目标，这些东西都是GNU的东西，但是我想，GNU搞出这些东西一定有其可取之处（等你的UNIX下的程序文件一多时你就会发现这些功能很有用了），这里只不过是说明了，如果你要书写这种功能，最好使用这种名字命名你的目标，这样规范一些，规范的好处就是——不用解释，大家都明白。而且如果你的makefile中有这些功能，一是很实用，二是可以显得你的makefile很专业（不是那种初学者的作品）。<br><br><br>四、检查规则<br><br>有时候，我们不想让我们的makefile中的规则执行起来，我们只想检查一下我们的命令，或是执行的序列。于是我们可以使用make命令的下述参数：<br><br>&#8220;-n&#8221;<br>&#8220;--just-print&#8221;<br>&#8220;--dry-run&#8221;<br>&#8220;--recon&#8221;<br>不执行参数，这些参数只是打印命令，不管目标是否更新，把规则和连带规则下的命令打印出来，但不执行，这些参数对于我们调试makefile很有用处。<br><br>&#8220;-t&#8221;<br>&#8220;--touch&#8221;<br>这个参数的意思就是把目标文件的时间更新，但不更改目标文件。也就是说，make假装编译目标，但不是真正的编译目标，只是把目标变成已编译过的状态。<br><br>&#8220;-q&#8221;<br>&#8220;--question&#8221;<br>这个参数的行为是找目标的意思，也就是说，如果目标存在，那么其什么也不会输出，当然也不会执行编译，如果目标不存在，其会打印出一条出错信息。<br><br>&#8220;-W &lt;file&gt;&#8221;<br>&#8220;--what-if=&lt;file&gt;&#8221;<br>&#8220;--assume-new=&lt;file&gt;&#8221;<br>&#8220;--new-file=&lt;file&gt;&#8221;<br>这个参数需要指定一个文件。一般是是源文件（或依赖文件），Make会根据规则推导来运行依赖于这个文件的命令，一般来说，可以和&#8220;-n&#8221;参数一同使用，来查看这个依赖文件所发生的规则命令。<br><br>另外一个很有意思的用法是结合&#8220;-p&#8221;和&#8220;-v&#8221;来输出makefile被执行时的信息（这个将在后面讲述）。<br><br><br>五、make的参数<br><br>下面列举了所有GNU make 3.80版的参数定义。其它版本和产商的make大同小异，不过其它产商的make的具体参数还是请参考各自的产品文档。<br><br>&#8220;-b&#8221;<br>&#8220;-m&#8221;<br>这两个参数的作用是忽略和其它版本make的兼容性。<br><br>&#8220;-B&#8221;<br>&#8220;--always-make&#8221;<br>认为所有的目标都需要更新（重编译）。<br><br>&#8220;-C &lt;dir&gt;&#8221;<br>&#8220;--directory=&lt;dir&gt;&#8221;<br>指定读取makefile的目录。如果有多个&#8220;-C&#8221;参数，make的解释是后面的路径以前面的作为相对路径，并以最后的目录作为被指定目录。如：&#8220;make &#8211;C ~hchen/test &#8211;C prog&#8221;等价于&#8220;make &#8211;C ~hchen/test/prog&#8221;。<br><br>&#8220;—debug[=&lt;options&gt;]&#8221;<br>输出make的调试信息。它有几种不同的级别可供选择，如果没有参数，那就是输出最简单的调试信息。下面是&lt;options&gt;的取值：<br>a —— 也就是all，输出所有的调试信息。（会非常的多）<br>b —— 也就是basic，只输出简单的调试信息。即输出不需要重编译的目标。<br>v —— 也就是verbose，在b选项的级别之上。输出的信息包括哪个makefile被解析，不需要被重编译的依赖文件（或是依赖目标）等。<br>i —— 也就是implicit，输出所以的隐含规则。<br>j —— 也就是jobs，输出执行规则中命令的详细信息，如命令的PID、返回码等。<br>m —— 也就是makefile，输出make读取makefile，更新makefile，执行makefile的信息。<br><br>&#8220;-d&#8221;<br>相当于&#8220;--debug=a&#8221;。<br><br>&#8220;-e&#8221;<br>&#8220;--environment-overrides&#8221;<br>指明环境变量的值覆盖makefile中定义的变量的值。<br><br>&#8220;-f=&lt;file&gt;&#8221;<br>&#8220;--file=&lt;file&gt;&#8221;<br>&#8220;--makefile=&lt;file&gt;&#8221;<br>指定需要执行的makefile。<br><br>&#8220;-h&#8221;<br>&#8220;--help&#8221;<br>显示帮助信息。<br><br>&#8220;-i&#8221;<br>&#8220;--ignore-errors&#8221;<br>在执行时忽略所有的错误。<br><br>&#8220;-I &lt;dir&gt;&#8221;<br>&#8220;--include-dir=&lt;dir&gt;&#8221;<br>指定一个被包含makefile的搜索目标。可以使用多个&#8220;-I&#8221;参数来指定多个目录。<br><br>&#8220;-j [&lt;jobsnum&gt;]&#8221;<br>&#8220;--jobs[=&lt;jobsnum&gt;]&#8221;<br>指同时运行命令的个数。如果没有这个参数，make运行命令时能运行多少就运行多少。如果有一个以上的&#8220;-j&#8221;参数，那么仅最后一个&#8220;-j&#8221;才是有效的。（注意这个参数在MS-DOS中是无用的）<br><br>&#8220;-k&#8221;<br>&#8220;--keep-going&#8221;<br>出错也不停止运行。如果生成一个目标失败了，那么依赖于其上的目标就不会被执行了。<br><br>&#8220;-l &lt;load&gt;&#8221;<br>&#8220;--load-average[=&lt;load]&#8221;<br>&#8220;—max-load[=&lt;load&gt;]&#8221;<br>指定make运行命令的负载。<br><br>&#8220;-n&#8221;<br>&#8220;--just-print&#8221;<br>&#8220;--dry-run&#8221;<br>&#8220;--recon&#8221;<br>仅输出执行过程中的命令序列，但并不执行。<br><br>&#8220;-o &lt;file&gt;&#8221;<br>&#8220;--old-file=&lt;file&gt;&#8221;<br>&#8220;--assume-old=&lt;file&gt;&#8221;<br>不重新生成的指定的&lt;file&gt;，即使这个目标的依赖文件新于它。<br><br>&#8220;-p&#8221;<br>&#8220;--print-data-base&#8221;<br>输出makefile中的所有数据，包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行 makefile，你可以使用&#8220;make -qp&#8221;命令。如果你想查看执行makefile前的预设变量和规则，你可以使用&#8220;make &#8211;p &#8211;f /dev/null&#8221;。这个参数输出的信息会包含着你的makefile文件的文件名和行号，所以，用这个参数来调试你的makefile会是很有用的，特别是当你的环境变量很复杂的时候。<br><br>&#8220;-q&#8221;<br>&#8220;--question&#8221;<br>不运行命令，也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新，如果是2则说明有错误发生。<br><br>&#8220;-r&#8221;<br>&#8220;--no-builtin-rules&#8221;<br>禁止make使用任何隐含规则。<br><br>&#8220;-R&#8221;<br>&#8220;--no-builtin-variabes&#8221;<br>禁止make使用任何作用于变量上的隐含规则。<br><br>&#8220;-s&#8221;<br>&#8220;--silent&#8221;<br>&#8220;--quiet&#8221;<br>在命令运行时不输出命令的输出。<br><br>&#8220;-S&#8221;<br>&#8220;--no-keep-going&#8221;<br>&#8220;--stop&#8221;<br>取消&#8220;-k&#8221;选项的作用。因为有些时候，make的选项是从环境变量&#8220;MAKEFLAGS&#8221;中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的&#8220;-k&#8221;选项失效。<br><br>&#8220;-t&#8221;<br>&#8220;--touch&#8221;<br>相当于UNIX的touch命令，只是把目标的修改日期变成最新的，也就是阻止生成目标的命令运行。<br><br>&#8220;-v&#8221;<br>&#8220;--version&#8221;<br>输出make程序的版本、版权等关于make的信息。<br><br>&#8220;-w&#8221;<br>&#8220;--print-directory&#8221;<br>输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用。<br><br>&#8220;--no-print-directory&#8221;<br>禁止&#8220;-w&#8221;选项。<br><br>&#8220;-W &lt;file&gt;&#8221;<br>&#8220;--what-if=&lt;file&gt;&#8221;<br>&#8220;--new-file=&lt;file&gt;&#8221;<br>&#8220;--assume-file=&lt;file&gt;&#8221;<br>假定目标&lt;file&gt;需要更新，如果和&#8220;-n&#8221;选项使用，那么这个参数会输出该目标更新时的运行动作。如果没有&#8220;-n&#8221;那么就像运行UNIX的&#8220;touch&#8221;命令一样，使得&lt;file&gt;的修改时间为当前时间。<br><br>&#8220;--warn-undefined-variables&#8221;<br>只要make发现有未定义的变量，那么就输出警告信息。</font></font></font></font></p>
<p><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><br></font></font></font></font></p>
</span>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59814.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-07-03 16:51 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59814.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个很好的makefile教程2（转载）</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59813.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Fri, 03 Jul 2009 08:50:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59813.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59813.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59813.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59813.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59813.html</trackback:ping><description><![CDATA[<br><font face=Verdana size=2>使用变量<br>————<br><br>在Makefile 中的定义的变量，就像是C/C++语言中的宏一样，他代表了一个文本字串，在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与 C/C++所不同的是，你可以在Makefile中改变其值。在Makefile中，变量可以使用在&#8220;目标&#8221;，&#8220;依赖目标&#8221;，&#8220;命令&#8221;或是 Makefile的其它部分中。<br><br>变量的命名字可以包含字符、数字，下划线（可以是数字开头），但不应该含有&#8220;:&#8221;、&#8220;#&#8221;、&#8220;=&#8221;或是空字符（空格、回车等）。变量是大小写敏感的，&#8220;foo&#8221;、&#8220;Foo&#8221;和&#8220;FOO&#8221;是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式，但我推荐使用大小写搭配的变量名，如：MakeFlags。这样可以避免和系统的变量冲突，而发生意外的事情。<br><br>有一些变量是很奇怪字串，如&#8220;$&lt;&#8221;、&#8220;$@&#8221;等，这些是自动化变量，我会在后面介绍。<br><br>一、变量的基础<br><br>变量在声明时需要给予初值，而在使用时，需要给在变量名前加上&#8220;$&#8221;符号，但最好用小括号&#8220;（）&#8221;或是大括号&#8220;{}&#8221;把变量给包括起来。如果你要使用真实的&#8220;$&#8221;字符，那么你需要用&#8220;$$&#8221;来表示。<br><br>变量可以使用在许多地方，如规则中的&#8220;目标&#8221;、&#8220;依赖&#8221;、&#8220;命令&#8221;以及新的变量中。先看一个例子：<br><br>objects = program.o foo.o utils.o<br>program : $(objects)<br>cc -o program $(objects)<br><br>$(objects) : defs.h<br><br>变量会在使用它的地方精确地展开，就像C/C++中的宏一样，例如：<br><br>foo = c<br>prog.o : prog.$(foo)<br>$(foo)$(foo) -$(foo) prog.$(foo)<br><br>展开后得到：<br><br>prog.o : prog.c<br>cc -c prog.c<br><br>当然，千万不要在你的Makefile中这样干，这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个&#8220;替代&#8221;的原理。<br><br>另外，给变量加上括号完全是为了更加安全地使用这个变量，在上面的例子中，如果你不想给变量加上括号，那也可以，但我还是强烈建议你给变量加上括号。<br><br><br>二、变量中的变量<br><br>在定义变量的值时，我们可以使用其它变量来构造变量的值，在Makefile中有两种方式来在用变量定义变量的值。<br><br>先看第一种方式，也就是简单的使用&#8220;=&#8221;号，在&#8220;=&#8221;左侧是变量，右侧是变量的值，右侧变量的值可以定义在文件的任何一处，也就是说，右侧中的变量不一定非要是已定义好的值，其也可以使用后面定义的值。如：<br><br>foo = $(bar)<br>bar = $(ugh)<br>ugh = Huh?<br><br>all:<br>echo $(foo)<br><br>我们执行&#8220;make all&#8221;将会打出变量$(foo)的值是&#8220;Huh?&#8221;（ $(foo)的值是$(bar)，$(bar)的值是$(ugh)，$(ugh)的值是&#8220;Huh?&#8221;）可见，变量是可以使用后面的变量来定义的。<br><br>这个功能有好的地方，也有不好的地方，好的地方是，我们可以把变量的真实值推到后面来定义，如：<br><br>CFLAGS = $(include_dirs) -O<br>include_dirs = -Ifoo -Ibar<br><br>当&#8220;CFLAGS&#8221;在命令中被展开时，会是&#8220;-Ifoo -Ibar -O&#8221;。但这种形式也有不好的地方，那就是递归定义，如：<br><br>CFLAGS = $(CFLAGS) -O<br><br>或：<br><br>A = $(B)<br>B = $(A)<br><br>这会让make陷入无限的变量展开过程中去，当然，我们的make是有能力检测这样的定义，并会报错。还有就是如果在变量中使用函数，那么，这种方式会让我们的make运行时非常慢，更糟糕的是，他会使用得两个make的函数&#8220;wildcard&#8221;和&#8220;shell&#8221;发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。<br><br>为了避免上面的这种方法，我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是&#8220;:=&#8221;操作符，如：<br><br>x := foo<br>y := $(x) bar<br>x := later<br><br>其等价于：<br><br>y := foo bar<br>x := later<br><br>值得一提的是，这种方法，前面的变量不能使用后面的变量，只能使用前面已定义好了的变量。如果是这样：<br><br>y := $(x) bar<br>x := foo<br><br>那么，y的值是&#8220;bar&#8221;，而不是&#8220;foo bar&#8221;。<br><br>上面都是一些比较简单的变量使用了，让我们来看一个复杂的例子，其中包括了make的函数、条件表达式和一个系统变量&#8220;MAKELEVEL&#8221;的使用：<br><br>ifeq (0,${MAKELEVEL})<br>cur-dir := $(shell pwd)<br>whoami := $(shell whoami)<br>host-type := $(shell arch)<br>MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}<br>endif<br><br>关于条件表达式和函数，我们在后面再说，对于系统变量&#8220;MAKELEVEL&#8221;，其意思是，如果我们的make有一个嵌套执行的动作（参见前面的&#8220;嵌套使用make&#8221;），那么，这个变量会记录了我们的当前Makefile的调用层数。<br><br>下面再介绍两个定义变量时我们需要知道的，请先看一个例子，如果我们要定义一个变量，其值是一个空格，那么我们可以这样来：<br><br>nullstring :=<br>space := $(nullstring) # end of the line<br><br>nullstring 是一个Empty变量，其中什么也没有，而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的，这里采用的技术很管用，先用一个 Empty变量来标明变量的值开始了，而后面采用&#8220;#&#8221;注释符来表示变量定义的终止，这样，我们可以定义出其值是一个空格的变量。请注意这里关于&#8220;#&#8221;的使用，注释符&#8220;#&#8221;的这种特性值得我们注意，如果我们这样定义一个变量：<br><br>dir := /foo/bar # directory to put the frobs in<br><br>dir这个变量的值是&#8220;/foo/bar&#8221;，后面还跟了4个空格，如果我们这样使用这样变量来指定别的目录——&#8220;$(dir)/file&#8221;那么就完蛋了。<br><br>还有一个比较有用的操作符是&#8220;?=&#8221;，先看示例：<br><br>FOO ?= bar<br><br>其含义是，如果FOO没有被定义过，那么变量FOO的值就是&#8220;bar&#8221;，如果FOO先前被定义过，那么这条语将什么也不做，其等价于：<br><br>ifeq ($(origin FOO), undefined)<br>FOO = bar<br>endif<br><br><br>三、变量高级用法<br><br>这里介绍两种变量的高级使用方法，第一种是变量值的替换。<br><br>我们可以替换变量中的共有的部分，其格式是&#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;。<br><br>还是看一个示例吧：<br><br>foo := a.o b.o c.o<br>bar := $(foo:.o=.c)<br><br>这个示例中，我们先定义了一个&#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;。<br><br>另外一种变量替换的技术是以&#8220;静态模式&#8221;（参见前面章节）定义的，如：<br><br>foo := a.o b.o c.o<br>bar := $(foo:%.o=%.c)<br><br>这依赖于被替换字串中的有相同的模式，模式中必须包含一个&#8220;%&#8221;字符，这个例子同样让$(bar)变量的值为&#8220;a.c b.c c.c&#8221;。 <br><br>第二种高级用法是——&#8220;把变量的值再当成变量&#8221;。先看一个例子：<br><br>x = y<br>y = z<br>a := $($(x))<br><br>在这个例子中，$(x)的值是&#8220;y&#8221;，所以$($(x))就是$(y)，于是$(a)的值就是&#8220;z&#8221;。（注意，是&#8220;x=y&#8221;，而不是&#8220;x=$(y)&#8221;）<br><br>我们还可以使用更多的层次：<br><br>x = y<br>y = z<br>z = u<br>a := $($($(x)))<br><br>这里的$(a)的值是&#8220;u&#8221;，相关的推导留给读者自己去做吧。<br><br>让我们再复杂一点，使用上&#8220;在变量定义中使用变量&#8221;的第一个方式，来看一个例子：<br><br>x = $(y)<br>y = z<br>z = Hello<br>a := $($(x))<br><br>这里的$($(x))被替换成了$($(y))，因为$(y)值是&#8220;z&#8221;，所以，最终结果是：a:=$(z)，也就是&#8220;Hello&#8221;。<br><br>再复杂一点，我们再加上函数：<br><br>x = variable1<br>variable2 := Hello<br>y = $(subst 1,2,$(x))<br>z = y<br>a := $($($(z)))<br><br>这个例子中，&#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;。（喔，好不容易）<br><br>在这种方式中，或要可以使用多个变量来组成一个变量的名字，然后再取其值：<br><br>first_second = Hello<br>a = first<br>b = second<br>all = $($a_$b)<br><br>这里的&#8220;$a_$b&#8221;组成了&#8220;first_second&#8221;，于是，$(all)的值就是&#8220;Hello&#8221;。<br><br>再来看看结合第一种技术的例子：<br><br>a_objects := a.o b.o c.o<br>1_objects := 1.o 2.o 3.o<br><br>sources := $($(a1)_objects:.o=.c)<br><br>这个例子中，如果$(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;。<br><br>再来看一个这种技术和&#8220;函数&#8221;与&#8220;条件语句&#8221;一同使用的例子：<br><br>ifdef do_sort<br>func := sort<br>else<br>func := strip<br>endif<br><br>bar := a d b g q c<br><br>foo := $($(func) $(bar))<br><br>这个示例中，如果定义了&#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函数。<br><br>当然，&#8220;把变量的值再当成变量&#8221;这种技术，同样可以用在操作符的左边：<br><br>dir = foo<br>$(dir)_sources := $(wildcard $(dir)/*.c)<br>define $(dir)_print<br>lpr $($(dir)_sources)<br>endef<br><br>这个例子中定义了三个变量：&#8220;dir&#8221;，&#8220;foo_sources&#8221;和&#8220;foo_print&#8221;。<br><br><br>四、追加变量值<br><br>我们可以使用&#8220;+=&#8221;操作符给变量追加值，如：<br><br>objects = main.o foo.o bar.o utils.o<br>objects += another.o<br><br>于是，我们的$(objects)值变成：&#8220;main.o foo.o bar.o utils.o another.o&#8221;（another.o被追加进去了）<br><br>使用&#8220;+=&#8221;操作符，可以模拟为下面的这种例子：<br><br>objects = main.o foo.o bar.o utils.o<br>objects := $(objects) another.o<br><br>所不同的是，用&#8220;+=&#8221;更为简洁。<br><br>如果变量之前没有定义过，那么，&#8220;+=&#8221;会自动变成&#8220;=&#8221;，如果前面有变量定义，那么&#8220;+=&#8221;会继承于前次操作的赋值符。如果前一次的是&#8220;:=&#8221;，那么&#8220;+=&#8221;会以&#8220;:=&#8221;作为其赋值符，如：<br><br>variable := value<br>variable += more<br><br>等价于：<br><br>variable := value<br>variable := $(variable) more<br><br>但如果是这种情况： <br><br>variable = value<br>variable += more<br><br>由于前次的赋值符是&#8220;=&#8221;，所以&#8220;+=&#8221;也会以&#8220;=&#8221;来做为赋值，那么岂不会发生变量的递补归定义，这是很不好的，所以make会自动为我们解决这个问题，我们不必担心这个问题。<br><br><br>五、override 指示符<br><br>如果有变量是通常make的命令行参数设置的，那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值，那么，你可以使用&#8220;override&#8221;指示符。其语法是：<br><br>override &lt;variable&gt; = &lt;value&gt;<br><br>override &lt;variable&gt; := &lt;value&gt;<br><br>当然，你还可以追加：<br><br>override &lt;variable&gt; += &lt;more text&gt;<br><br>对于多行的变量定义，我们用define指示符，在define指示符前，也同样可以使用ovveride指示符，如：<br><br>override define foo<br>bar<br>endef<br><br>六、多行变量<br><br>还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行，这有利于定义一系列的命令（前面我们讲过&#8220;命令包&#8221;的技术就是利用这个关键字）。<br><br>define 指示符后面跟的是变量的名字，而重起一行定义变量的值，定义是以endef关键字结束。其工作方式和&#8220;=&#8221;操作符一样。变量的值可以包含函数、命令、文字，或是其它变量。因为命令需要以[Tab]键开头，所以如果你用define定义的命令变量中没有以[Tab]键开头，那么make就不会把其认为是命令。<br><br>下面的这个示例展示了define的用法：<br><br>define two-lines<br>echo foo<br>echo $(bar)<br>endef<br><br><br>七、环境变量<br><br>make 运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中，但是如果Makefile中已定义了这个变量，或是这个变量由make命令行带入，那么系统的环境变量的值将被覆盖。（如果make指定了&#8220;-e&#8221;参数，那么，系统环境变量将覆盖Makefile中定义的变量）<br><br>因此，如果我们在环境变量中设置了&#8220;CFLAGS&#8221;环境变量，那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS，那么则会使用Makefile中的这个变量，如果没有定义则使用系统环境变量的值，一个共性和个性的统一，很像&#8220;全局变量&#8221;和&#8220;局部变量&#8221;的特性。<br><br>当make嵌套调用时（参见前面的&#8220;嵌套调用&#8221;章节），上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然，默认情况下，只有通过命令行设置的变量会被传递。而定义在文件中的变量，如果要向下层 Makefile传递，则需要使用exprot关键字来声明。（参见前面章节）<br><br>当然，我并不推荐把许多的变量都定义在系统环境中，这样，在我们执行不用的Makefile时，拥有的是同一套系统变量，这可能会带来更多的麻烦。<br><br><br>八、目标变量<br><br>前面我们所讲的在Makefile中定义的变量都是&#8220;全局变量&#8221;，在整个文件，我们都可以访问这些变量。当然，&#8220;自动化变量&#8221;除外，如&#8220;$&lt;&#8221;等这种类量的自动化变量就属于&#8220;规则型变量&#8221;，这种变量的值依赖于规则的目标和依赖目标的定义。<br><br>当然，我样同样可以为某个目标设置局部变量，这种变量被称为&#8220;Target-specific Variable&#8221;，它可以和&#8220;全局变量&#8221;同名，因为它的作用范围只在这条规则以及连带规则中，所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。<br><br>其语法是：<br><br>&lt;target ...&gt; : &lt;variable-assignment&gt;<br><br>&lt;target ...&gt; : overide &lt;variable-assignment&gt;<br><br>&lt;variable-assignment&gt;可以是前面讲过的各种赋值表达式，如&#8220;=&#8221;、&#8220;:=&#8221;、&#8220;+=&#8221;或是&#8220;？=&#8221;。第二个语法是针对于make命令行带入的变量，或是系统环境变量。<br><br>这个特性非常的有用，当我们设置了这样一个变量，这个变量会作用到由这个目标所引发的所有的规则中去。如：<br><br>prog : CFLAGS = -g<br>prog : prog.o foo.o bar.o<br>$(CC) $(CFLAGS) prog.o foo.o bar.o<br><br>prog.o : prog.c<br>$(CC) $(CFLAGS) prog.c<br><br>foo.o : foo.c<br>$(CC) $(CFLAGS) foo.c<br><br>bar.o : bar.c<br>$(CC) $(CFLAGS) bar.c<br><br>在这个示例中，不管全局的$(CFLAGS)的值是什么，在prog目标，以及其所引发的所有规则中（prog.o foo.o bar.o的规则），$(CFLAGS)的值都是&#8220;-g&#8221;<br><br><br>九、模式变量<br><br>在GNU的make中，还支持模式变量（Pattern-specific Variable），通过上面的目标变量中，我们知道，变量可以定义在某个目标上。模式变量的好处就是，我们可以给定一种&#8220;模式&#8221;，可以把变量定义在符合这种模式的所有目标上。<br><br>我们知道，make的&#8220;模式&#8221;一般是至少含有一个&#8220;%&#8221;的，所以，我们可以以如下方式给所有以[.o]结尾的目标定义目标变量：<br><br>%.o : CFLAGS = -O<br><br>同样，模式变量的语法和&#8220;目标变量&#8221;一样：<br><br>&lt;pattern ...&gt; : &lt;variable-assignment&gt;<br><br>&lt;pattern ...&gt; : override &lt;variable-assignment&gt;<br><br>override同样是针对于系统环境传入的变量，或是make命令行指定的变量。<br></font>
<p><font face=Verdana size=2>使用条件判断<br>——————<br><br>使用条件判断，可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值，或是比较变量和常量的值。<br><br>一、示例<br><br>下面的例子，判断$(CC)变量是否&#8220;gcc&#8221;，如果是的话，则使用GNU函数编译目标。<br><br>libs_for_gcc = -lgnu<br>normal_libs =<br><br>foo: $(objects)<br>ifeq ($(CC),gcc)<br>$(CC) -o foo $(objects) $(libs_for_gcc)<br>else<br>$(CC) -o foo $(objects) $(normal_libs)<br>endif<br><br>可见，在上面示例的这个规则中，目标&#8220;foo&#8221;可以根据变量&#8220;$(CC)&#8221;值来选取不同的函数库来编译程序。<br><br>我们可以从上面的示例中看到三个关键字：ifeq、else和endif。ifeq的意思表示条件语句的开始，并指定一个条件表达式，表达式包含两个参数，以逗号分隔，表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条件语句的结束，任何一个条件表达式都应该以endif结束。<br><br>当我们的变量$(CC)值是&#8220;gcc&#8221;时，目标foo的规则是：<br><br>foo: $(objects)<br>$(CC) -o foo $(objects) $(libs_for_gcc)<br><br>而当我们的变量$(CC)值不是&#8220;gcc&#8221;时（比如&#8220;cc&#8221;），目标foo的规则是：<br><br>foo: $(objects)<br>$(CC) -o foo $(objects) $(normal_libs)<br><br>当然，我们还可以把上面的那个例子写得更简洁一些：<br><br>libs_for_gcc = -lgnu<br>normal_libs =<br><br>ifeq ($(CC),gcc)<br>libs=$(libs_for_gcc)<br>else<br>libs=$(normal_libs)<br>endif<br><br>foo: $(objects)<br>$(CC) -o foo $(objects) $(libs)<br><br><br>二、语法<br><br>条件表达式的语法为：<br><br>&lt;conditional-directive&gt;<br>&lt;text-if-true&gt;<br>endif<br><br>以及：<br><br>&lt;conditional-directive&gt;<br>&lt;text-if-true&gt;<br>else<br>&lt;text-if-false&gt;<br>endif<br><br>其中&lt;conditional-directive&gt;表示条件关键字，如&#8220;ifeq&#8221;。这个关键字有四个。<br><br>第一个是我们前面所见过的&#8220;ifeq&#8221;<br><br>ifeq (&lt;arg1&gt;, &lt;arg2&gt; ) <br>ifeq '&lt;arg1&gt;' '&lt;arg2&gt;' <br>ifeq "&lt;arg1&gt;" "&lt;arg2&gt;" <br>ifeq "&lt;arg1&gt;" '&lt;arg2&gt;' <br>ifeq '&lt;arg1&gt;' "&lt;arg2&gt;" <br><br>比较参数&#8220;arg1&#8221;和&#8220;arg2&#8221;的值是否相同。当然，参数中我们还可以使用make的函数。如：<br><br>ifeq ($(strip $(foo)),)<br>&lt;text-if-empty&gt;<br>endif<br><br>这个示例中使用了&#8220;strip&#8221;函数，如果这个函数的返回值是空（Empty），那么&lt;text-if-empty&gt;就生效。<br><br>第二个条件关键字是&#8220;ifneq&#8221;。语法是：<br><br>ifneq (&lt;arg1&gt;, &lt;arg2&gt; ) <br>ifneq '&lt;arg1&gt;' '&lt;arg2&gt;' <br>ifneq "&lt;arg1&gt;" "&lt;arg2&gt;" <br>ifneq "&lt;arg1&gt;" '&lt;arg2&gt;' <br>ifneq '&lt;arg1&gt;' "&lt;arg2&gt;" <br><br>其比较参数&#8220;arg1&#8221;和&#8220;arg2&#8221;的值是否相同，如果不同，则为真。和&#8220;ifeq&#8221;类似。<br><br>第三个条件关键字是&#8220;ifdef&#8221;。语法是：<br><br>ifdef &lt;variable-name&gt; <br><br>如果变量&lt;variable-name&gt;的值非空，那到表达式为真。否则，表达式为假。当然，&lt;variable-name&gt;同样可以是一个函数的返回值。注意，ifdef只是测试一个变量是否有值，其并不会把变量扩展到当前位置。还是来看两个例子：<br><br>示例一：<br>bar =<br>foo = $(bar)<br>ifdef foo<br>frobozz = yes<br>else<br>frobozz = no<br>endif<br><br>示例二：<br>foo =<br>ifdef foo<br>frobozz = yes<br>else<br>frobozz = no<br>endif<br><br>第一个例子中，&#8220;$(frobozz)&#8221;值是&#8220;yes&#8221;，第二个则是&#8220;no&#8221;。<br><br>第四个条件关键字是&#8220;ifndef&#8221;。其语法是：<br><br>ifndef &lt;variable-name&gt;<br><br>这个我就不多说了，和&#8220;ifdef&#8221;是相反的意思。<br><br>在&lt;conditional-directive&gt;这一行上，多余的空格是被允许的，但是不能以[Tab]键做为开始（不然就被认为是命令）。而注释符&#8220;#&#8221;同样也是安全的。&#8220;else&#8221;和&#8220;endif&#8221;也一样，只要不是以[Tab]键开始就行了。<br><br>特别注意的是，make是在读取Makefile时就计算条件表达式的值，并根据条件表达式的值来选择语句，所以，你最好不要把自动化变量（如&#8220;$@&#8221;等）放入条件表达式中，因为自动化变量是在运行时才有的。<br><br>而且，为了避免混乱，make不允许把整个条件语句分成两部分放在不同的文件中。</font></p>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59813.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-07-03 16:50 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59813.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个很好的makefile教程1（转载）</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59812.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Fri, 03 Jul 2009 08:49:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59812.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59812.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59812.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59812.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59812.html</trackback:ping><description><![CDATA[<font face=Verdana size=2><br><strong style="FONT-SIZE: 14pt">跟我一起写 Makefile</strong><br><br><br>陈皓 (CSDN)<br><br>概述<br>——<br><br>什么是makefile？或许很多Winodws的程序员都不知道这个东西，因为那些Windows的IDE都为你做了这个工作，但我觉得要作一个好的和 professional的程序员，makefile还是要懂。这就好像现在有这么多的HTML的编辑器，但如果你想成为一个专业人士，你还是要了解 HTML的标识的含义。特别在Unix下的软件编译，你就不能不自己写makefile了，会不会写makefile，从一个侧面说明了一个人是否具备完成大型工程的能力。<br><br>因为，makefile关系到了整个工程的编译规则。一个工程中的源文件不计数，其按类型、功能、模块分别放在若干个目录中，makefile定义了一系列的规则来指定，哪些文件需要先编译，哪些文件需要后编译，哪些文件需要重新编译，甚至于进行更复杂的功能操作，因为 makefile就像一个Shell脚本一样，其中也可以执行操作系统的命令。<br><br>makefile带来的好处就是——&#8220;自动化编译&#8221;，一旦写好，只需要一个make命令，整个工程完全自动编译，极大的提高了软件开发的效率。make是一个命令工具，是一个解释makefile中指令的命令工具，一般来说，大多数的IDE都有这个命令，比如：Delphi的make，Visual C++的nmake，Linux下GNU的make。可见，makefile都成为了一种在工程方面的编译方法。<br><br>现在讲述如何写 makefile的文章比较少，这是我想写这篇文章的原因。当然，不同产商的make各不相同，也有不同的语法，但其本质都是在&#8220;文件依赖性&#8221;上做文章，这里，我仅对GNU的make进行讲述，我的环境是RedHat Linux 8.0，make的版本是3.80。必竟，这个make是应用最为广泛的，也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的（POSIX.2）。<br><br>在这篇文档中，将以C/C++的源码作为我们基础，所以必然涉及一些关于C/C++的编译的知识，相关于这方面的内容，还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。<br><br><br><br>关于程序的编译和链接<br>——————————<br><br>在此，我想多说关于程序编译的一些规范和方法，一般来说，无论是C、C++、还是pas，首先要把源文件编译成中间代码文件，在Windows下也就是 .obj 文件，UNIX下是 .o 文件，即 Object File，这个动作叫做编译（compile）。然后再把大量的Object File合成执行文件，这个动作叫作链接（link）。<br><br>编译时，编译器需要的是语法的正确，函数与变量的声明的正确。对于后者，通常是你需要告诉编译器头文件的所在位置（头文件中应该只是声明，而定义应该放在C/C++文件中），只要所有的语法正确，编译器就可以编译出中间目标文件。一般来说，每个源文件都应该对应于一个中间目标文件（O文件或是OBJ文件）。<br><br>链接时，主要是链接函数和全局变量，所以，我们可以使用这些中间目标文件（O文件或是OBJ文件）来链接我们的应用程序。链接器并不管函数所在的源文件，只管函数的中间目标文件（Object File），在大多数时候，由于源文件太多，编译生成的中间目标文件太多，而在链接时需要明显地指出中间目标文件名，这对于编译很不方便，所以，我们要给中间目标文件打个包，在Windows下这种包叫&#8220;库文件&#8221;（Library File)，也就是 .lib 文件，在UNIX下，是Archive File，也就是 .a 文件。<br><br>总结一下，源文件首先会生成中间目标文件，再由中间目标文件生成执行文件。在编译时，编译器只检测程序语法，和函数、变量是否被声明。如果函数未被声明，编译器会给出一个警告，但可以生成Object File。而在链接程序时，链接器会在所有的Object File中找寻函数的实现，如果找不到，那到就会报链接错误码（Linker Error），在VC下，这种错误一般是：Link 2001错误，意思说是说，链接器未能找到函数的实现。你需要指定函数的Object File.<br><br>好，言归正传，GNU的make有许多的内容，闲言少叙，还是让我们开始吧。<br><br><br><br>Makefile 介绍<br>———————<br><br>make命令执行时，需要一个 Makefile 文件，以告诉make命令需要怎么样的去编译和链接程序。<br><br>首先，我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册，在这个示例中，我们的工程有8个C文件，和3个头文件，我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是：<br>1）如果这个工程没有编译过，那么我们的所有C文件都要编译并被链接。<br>2）如果这个工程的某几个C文件被修改，那么我们只编译被修改的C文件，并链接目标程序。<br>3）如果这个工程的头文件被改变了，那么我们需要编译引用了这几个头文件的C文件，并链接目标程序。<br><br>只要我们的Makefile写得够好，所有的这一切，我们只用一个make命令就可以完成，make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译，从而自己编译所需要的文件和链接目标程序。<br><br><br>一、Makefile的规则<br><br>在讲述这个Makefile之前，还是让我们先来粗略地看一看Makefile的规则。<br><br>target ... : prerequisites ...<br>command<br>...<br>...<br><br>target也就是一个目标文件，可以是Object File，也可以是执行文件。还可以是一个标签（Label），对于标签这种特性，在后续的&#8220;伪目标&#8221;章节中会有叙述。<br><br>prerequisites就是，要生成那个target所需要的文件或是目标。<br><br>command也就是make需要执行的命令。（任意的Shell命令）<br><br>这是一个文件的依赖关系，也就是说，target这一个或多个的目标文件依赖于prerequisites中的文件，其生成规则定义在command中。说白一点就是说，prerequisites中如果有一个以上的文件比target文件要新的话，command所定义的命令就会被执行。这就是 Makefile的规则。也就是Makefile中最核心的内容。<br><br>说到底，Makefile的东西就是这样一点，好像我的这篇文档也该结束了。呵呵。还不尽然，这是Makefile的主线和核心，但要写好一个Makefile还不够，我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。：）<br><br><br>二、一个示例<br><br>正如前面所说的，如果一个工程有3个头文件，和8个C文件，我们为了完成前面所述的那三个规则，我们的Makefile应该是下面的这个样子的。<br><br>edit : main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br>cc -o edit main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br><br>main.o : main.c defs.h<br>cc -c main.c<br>kbd.o : kbd.c defs.h command.h<br>cc -c kbd.c<br>command.o : command.c defs.h command.h<br>cc -c command.c<br>display.o : display.c defs.h buffer.h<br>cc -c display.c<br>insert.o : insert.c defs.h buffer.h<br>cc -c insert.c<br>search.o : search.c defs.h buffer.h<br>cc -c search.c<br>files.o : files.c defs.h buffer.h command.h<br>cc -c files.c<br>utils.o : utils.c defs.h<br>cc -c utils.c<br>clean :<br>rm edit main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br><br>反斜杠（\）是换行符的意思。这样比较便于Makefile的易读。我们可以把这个内容保存在文件为&#8220;Makefile&#8221;或&#8220;makefile&#8221;的文件中，然后在该目录下直接输入命令&#8220;make&#8221;就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件，那么，只要简单地执行一下&#8220;make clean&#8221;就可以了。<br><br>在这个makefile中，目标文件（target）包含：执行文件edit和中间目标文件（*.o），依赖文件（prerequisites）就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件，而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的，换言之，目标文件是哪些文件更新的。<br><br>在定义好依赖关系后，后续的那一行定义了如何生成目标文件的操作系统命令，一定要以一个Tab键作为开头。记住，make并不管命令是怎么工作的，他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期，如果prerequisites文件的日期要比targets文件的日期要新，或者target不存在的话，那么，make就会执行后续定义的命令。<br><br>这里要说明一点的是，clean不是一个文件，它只不过是一个动作名字，有点像C语言中的lable一样，其冒号后什么也没有，那么，make就不会自动去找文件的依赖性，也就不会自动执行其后所定义的命令。要执行其后的命令，就要在make命令后明显得指出这个lable的名字。这样的方法非常有用，我们可以在一个makefile中定义不用的编译或是和编译无关的命令，比如程序的打包，程序的备份，等等。<br><br><br><br>三、make是如何工作的<br><br>在默认的方式下，也就是我们只输入make命令。那么，<br><br>1、make会在当前目录下找名字叫&#8220;Makefile&#8221;或&#8220;makefile&#8221;的文件。<br>2、如果找到，它会找文件中的第一个目标文件（target），在上面的例子中，他会找到&#8220;edit&#8221;这个文件，并把这个文件作为最终的目标文件。<br>3、如果edit文件不存在，或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新，那么，他就会执行后面所定义的命令来生成edit这个文件。<br>4、如果edit所依赖的.o文件也存在，那么make会在当前文件中找目标为.o文件的依赖性，如果找到则再根据那一个规则生成.o文件。（这有点像一个堆栈的过程）<br>5、当然，你的C文件和H文件是存在的啦，于是make会生成 .o 文件，然后再用 .o 文件生命make的终极任务，也就是执行文件edit了。<br><br>这就是整个make的依赖性，make会一层又一层地去找文件的依赖关系，直到最终编译出第一个目标文件。在找寻的过程中，如果出现错误，比如最后被依赖的文件找不到，那么make就会直接退出，并报错，而对于所定义的命令的错误，或是编译不成功，make根本不理。make只管文件的依赖性，即，如果在我找了依赖关系之后，冒号后面的文件还是不在，那么对不起，我就不工作啦。<br><br>通过上述分析，我们知道，像clean这种，没有被第一个目标文件直接或间接关联，那么它后面所定义的命令将不会被自动执行，不过，我们可以显示要make执行。即命令——&#8220;make clean&#8221;，以此来清除所有的目标文件，以便重编译。<br><br>于是在我们编程中，如果这个工程已被编译过了，当我们修改了其中一个源文件，比如file.c，那么根据我们的依赖性，我们的目标file.o会被重编译（也就是在这个依性关系后面所定义的命令），于是file.o的文件也是最新的啦，于是file.o的文件修改时间要比edit要新，所以edit也会被重新链接了（详见edit目标文件后定义的命令）。<br><br>而如果我们改变了&#8220;command.h&#8221;，那么，kdb.o、command.o和files.o都会被重编译，并且，edit会被重链接。<br><br><br>四、makefile中使用变量<br><br>在上面的例子中，先让我们看看edit的规则：<br><br>edit : main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br>cc -o edit main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br><br>我们可以看到[.o]文件的字符串被重复了两次，如果我们的工程需要加入一个新的[.o]文件，那么我们需要在两个地方加（应该是三个地方，还有一个地方在 clean中）。当然，我们的makefile并不复杂，所以在两个地方加也不累，但如果makefile变得复杂，那么我们就有可能会忘掉一个需要加入的地方，而导致编译失败。所以，为了makefile的易维护，在makefile中我们可以使用变量。makefile的变量也就是一个字符串，理解成 C语言中的宏可能会更好。<br><br>比如，我们声明一个变量，叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ，反正不管什么啦，只要能够表示obj文件就行了。我们在makefile一开始就这样定义：<br><br>objects = main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br><br>于是，我们就可以很方便地在我们的makefile中以&#8220;$(objects)&#8221;的方式来使用这个变量了，于是我们的改良版makefile就变成下面这个样子：<br><br>objects = main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br><br>edit : $(objects)<br>cc -o edit $(objects)<br>main.o : main.c defs.h<br>cc -c main.c<br>kbd.o : kbd.c defs.h command.h<br>cc -c kbd.c<br>command.o : command.c defs.h command.h<br>cc -c command.c<br>display.o : display.c defs.h buffer.h<br>cc -c display.c<br>insert.o : insert.c defs.h buffer.h<br>cc -c insert.c<br>search.o : search.c defs.h buffer.h<br>cc -c search.c<br>files.o : files.c defs.h buffer.h command.h<br>cc -c files.c<br>utils.o : utils.c defs.h<br>cc -c utils.c<br>clean :<br>rm edit $(objects)<br><br><br>于是如果有新的 .o 文件加入，我们只需简单地修改一下 objects 变量就可以了。<br><br>关于变量更多的话题，我会在后续给你一一道来。<br><br><br>五、让make自动推导<br><br>GNU的make很强大，它可以自动推导文件以及文件依赖关系后面的命令，于是我们就没必要去在每一个[.o]文件后都写上类似的命令，因为，我们的make会自动识别，并自己推导命令。<br><br>只要make看到一个[.o]文件，它就会自动的把[.c]文件加在依赖关系中，如果make找到一个whatever.o，那么whatever.c，就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来，于是，我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。<br><br><br>objects = main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br><br>edit : $(objects)<br>cc -o edit $(objects)<br><br>main.o : defs.h<br>kbd.o : defs.h command.h<br>command.o : defs.h command.h<br>display.o : defs.h buffer.h<br>insert.o : defs.h buffer.h<br>search.o : defs.h buffer.h<br>files.o : defs.h buffer.h command.h<br>utils.o : defs.h<br><br>.PHONY : clean<br>clean :<br>rm edit $(objects)<br><br>这种方法，也就是make的&#8220;隐晦规则&#8221;。上面文件内容中，&#8220;.PHONY&#8221;表示，clean是个伪目标文件。<br><br>关于更为详细的&#8220;隐晦规则&#8221;和&#8220;伪目标文件&#8221;，我会在后续给你一一道来。<br><br><br>六、另类风格的makefile<br><br>即然我们的make可以自动推导命令，那么我看到那堆[.o]和[.h]的依赖就有点不爽，那么多的重复的[.h]，能不能把其收拢起来，好吧，没有问题，这个对于make来说很容易，谁叫它提供了自动推导命令和文件的功能呢？来看看最新风格的makefile吧。<br><br>objects = main.o kbd.o command.o display.o \<br>insert.o search.o files.o utils.o<br><br>edit : $(objects)<br>cc -o edit $(objects)<br><br>$(objects) : defs.h<br>kbd.o command.o files.o : command.h<br>display.o insert.o search.o files.o : buffer.h<br><br>.PHONY : clean<br>clean :<br>rm edit $(objects)<br><br>这种风格，让我们的makefile变得很简单，但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的，一是文件的依赖关系看不清楚，二是如果文件一多，要加入几个新的.o文件，那就理不清楚了。</font>
<p><font face="verdana, arial, helvetica"><br></font></p>
<p><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica" size=2>七、清空目标文件的规则<br><br>每个Makefile中都应该写一个清空目标文件（.o和执行文件）的规则，这不仅便于重编译，也很利于保持文件的清洁。这是一个&#8220;修养&#8221;（呵呵，还记得我的《编程修养》吗）。一般的风格都是：<br><br>clean:<br>rm edit $(objects)<br><br>更为稳健的做法是：<br><br>.PHONY : clean<br>clean :<br>-rm edit $(objects)<br><br>前面说过，.PHONY意思表示clean是一个&#8220;伪目标&#8221;，。而在rm命令前面加了一个小减号的意思就是，也许某些文件出现问题，但不要管，继续做后面的事。当然，clean的规则不要放在文件的开头，不然，这就会变成make的默认目标，相信谁也不愿意这样。不成文的规矩是——&#8220;clean从来都是放在文件的最后&#8221;。<br><br><br>上面就是一个makefile的概貌，也是makefile的基础，下面还有很多makefile的相关细节，准备好了吗？准备好了就来。<br><br>Makefile 总述<br>———————<br><br>一、Makefile里有什么？<br><br>Makefile里主要包含了五个东西：显式规则、隐晦规则、变量定义、文件指示和注释。<br><br>1、显式规则。显式规则说明了，如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出，要生成的文件，文件的依赖文件，生成的命令。<br><br>2、隐晦规则。由于我们的make有自动推导的功能，所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile，这是由make所支持的。<br><br>3、变量的定义。在Makefile中我们要定义一系列的变量，变量一般都是字符串，这个有点你C语言中的宏，当Makefile被执行时，其中的变量都会被扩展到相应的引用位置上。<br><br>4、文件指示。其包括了三个部分，一个是在一个Makefile中引用另一个Makefile，就像C语言中的include一样；另一个是指根据某些情况指定Makefile中的有效部分，就像C语言中的预编译#if一样；还有就是定义一个多行的命令。有关这一部分的内容，我会在后续的部分中讲述。<br><br>5、注释。Makefile中只有行注释，和UNIX的Shell脚本一样，其注释是用&#8220;#&#8221;字符，这个就像C/C++中的&#8220;//&#8221;一样。如果你要在你的Makefile中使用&#8220;#&#8221;字符，可以用反斜框进行转义，如：&#8220;\#&#8221;。<br><br>最后，还值得一提的是，在Makefile中的命令，必须要以[Tab]键开始。<br><br><br>二、Makefile的文件名<br><br>默认的情况下，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;这两种默认文件名。<br><br>当然，你可以使用别的文件名来书写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。<br><br><br>三、引用其它的Makefile<br><br>在Makefile使用include关键字可以把别的Makefile包含进来，这很像C语言的＃i nclude，被包含的文件会原模原样的放在当前文件的包含位置。include的语法是：<br><br>include &lt;filename&gt;<br><br>filename可以是当前操作系统Shell的文件模式（可以保含路径和通配符）<br><br>在include 前面可以有一些空字符，但是绝不能是[Tab]键开始。include和&lt;filename&gt;可以用一个或多个空格隔开。举个例子，你有这样几个Makefile：a.mk、b.mk、c.mk，还有一个文件叫foo.make，以及一个变量$(bar)，其包含了e.mk和f.mk，那么，下面的语句：<br><br>include foo.make *.mk $(bar)<br><br>等价于：<br><br>include foo.make a.mk b.mk c.mk e.mk f.mk<br><br>make 命令开始时，会把找寻include所指出的其它Makefile，并把其内容安置在当前的位置。就好像C/C++的＃i nclude指令一样。如果文件都没有指定绝对路径或是相对路径的话，make会在当前目录下首先寻找，如果当前目录下没有找到，那么，make还会在下面的几个目录下找：<br><br>1、如果make执行时，有&#8220;-I&#8221;或&#8220;--include-dir&#8221;参数，那么make就会在这个参数所指定的目录下去寻找。<br>2、如果目录&lt;prefix&gt;/include（一般是：/usr/local/bin或/usr/include）存在的话，make也会去找。<br><br>如果有文件没有找到的话，make会生成一条警告信息，但不会马上出现致命错误。它会继续载入其它的文件，一旦完成makefile的读取，make会再重试这些没有找到，或是不能读取的文件，如果还是不行，make才会出现一条致命信息。如果你想让make不理那些无法读取的文件，而继续执行，你可以在 include前加一个减号&#8220;-&#8221;。如：<br><br>-include &lt;filename&gt;<br>其表示，无论include过程中出现什么错误，都不要报错继续执行。和其它版本make兼容的相关命令是sinclude，其作用和这一个是一样的。<br><br><br>四、环境变量 MAKEFILES <br><br>如果你的当前环境中定义了环境变量MAKEFILES，那么，make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的 Makefile，用空格分隔。只是，它和include不同的是，从这个环境变中引入的Makefile的&#8220;目标&#8221;不会起作用，如果环境变量中定义的文件发现错误，make也会不理。<br><br>但是在这里我还是建议不要使用这个环境变量，因为只要这个变量一被定义，那么当你使用make时，所有的 Makefile都会受到它的影响，这绝不是你想看到的。在这里提这个事，只是为了告诉大家，也许有时候你的Makefile出现了怪事，那么你可以看看当前环境中有没有定义这个变量。<br><br><br>五、make的工作方式<br><br>GNU的make工作时的执行步骤入下：（想来其它的make也是类似）<br><br>1、读入所有的Makefile。<br>2、读入被include的其它Makefile。<br>3、初始化文件中的变量。<br>4、推导隐晦规则，并分析所有规则。<br>5、为所有的目标文件创建依赖关系链。<br>6、根据依赖关系，决定哪些目标要重新生成。<br>7、执行生成命令。<br><br>1- 5步为第一个阶段，6-7为第二个阶段。第一个阶段中，如果定义的变量被使用了，那么，make会把其展开在使用的位置。但make并不会完全马上展开， make使用的是拖延战术，如果变量出现在依赖关系的规则中，那么仅当这条依赖被决定要使用了，变量才会在其内部展开。<br><br>当然，这个工作方式你不一定要清楚，但是知道这个方式你也会对make更为熟悉。有了这个基础，后续部分也就容易看懂了。<br><br><br><br>书写规则<br>————<br><br>规则包含两个部分，一个是依赖关系，一个是生成目标的方法。<br><br>在Makefile 中，规则的顺序是很重要的，因为，Makefile中只应该有一个最终目标，其它的目标都是被这个目标所连带出来的，所以一定要让make知道你的最终目标是什么。一般来说，定义在Makefile中的目标可能会有很多，但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个，那么，第一个目标会成为最终的目标。make所完成的也就是这个目标。<br><br>好了，还是让我们来看一看如何书写规则。<br><br><br>一、规则举例<br><br>foo.o : foo.c defs.h # foo模块<br>cc -c -g foo.c<br><br>看到这个例子，各位应该不是很陌生了，前面也已说过，foo.o是我们的目标，foo.c和defs.h是目标所依赖的源文件，而只有一个命令&#8220;cc -c -g foo.c&#8221;（以Tab键开头）。这个规则告诉我们两件事：<br><br>1、文件的依赖关系，foo.o依赖于foo.c和defs.h的文件，如果foo.c和defs.h的文件日期要比foo.o文件日期要新，或是foo.o不存在，那么依赖关系发生。<br>2、如果生成（或更新）foo.o文件。也就是那个cc命令，其说明了，如何生成foo.o这个文件。（当然foo.c文件include了defs.h文件）<br><br><br>二、规则的语法<br><br>targets : prerequisites<br>command<br>...<br><br>或是这样： <br><br>targets : prerequisites ; command<br>command<br>...<br><br>targets是文件名，以空格分开，可以使用通配符。一般来说，我们的目标基本上是一个文件，但也有可能是多个文件。<br><br>command是命令行，如果其不与&#8220;target: prerequisites&#8221;在一行，那么，必须以[Tab键]开头，如果和prerequisites在一行，那么可以用分号做为分隔。（见上）<br><br>prerequisites也就是目标所依赖的文件（或依赖目标）。如果其中的某个文件要比目标文件要新，那么，目标就被认为是&#8220;过时的&#8221;，被认为是需要重生成的。这个在前面已经讲过了。<br><br>如果命令太长，你可以使用反斜框（&#8216;\&#8217;）作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事，文件的依赖关系和如何成成目标文件。<br><br>一般来说，make会以UNIX的标准Shell，也就是/bin/sh来执行命令。<br><br><br>三、在规则中使用通配符<br><br>如果我们想定义一系列比较类似的文件，我们很自然地就想起使用通配符。make支持三各通配符：&#8220;*&#8221;，&#8220;?&#8221;和&#8220;[...]&#8221;。这是和Unix的B-Shell是相同的。<br><br>波浪号（&#8220;~&#8221;）字符在文件名中也有比较特殊的用途。如果是&#8220;~/test&#8221;，这就表示当前用户的$HOME目录下的test目录。而 &#8220;~hchen/test&#8221;则表示用户hchen的宿主目录下的test目录。（这些都是Unix下的小知识了，make也支持）而在Windows或是 MS-DOS下，用户没有宿主目录，那么波浪号所指的目录则根据环境变量&#8220;HOME&#8221;而定。<br><br>通配符代替了你一系列的文件，如&#8220;*.c&#8221;表示所以后缀为c的文件。一个需要我们注意的是，如果我们的文件名中有通配符，如：&#8220;*&#8221;，那么可以用转义字符&#8220;\&#8221;，如&#8220;\*&#8221;来表示真实的&#8220;*&#8221;字符，而不是任意长度的字符串。<br><br>好吧，还是先来看几个例子吧：<br><br>clean:<br>rm -f *.o<br><br>上面这个例子我不不多说了，这是操作系统Shell所支持的通配符。这是在命令中的通配符。<br><br>print: *.c<br>lpr -p $?<br>touch print<br><br>上面这个例子说明了通配符也可以在我们的规则中，目标print依赖于所有的[.c]文件。其中的&#8220;$?&#8221;是一个自动化变量，我会在后面给你讲述。<br><br>objects = *.o<br><br>上面这个例子，表示了，通符同样可以用在变量中。并不是说[*.o]会展开，不！objects的值就是&#8220;*.o&#8221;。Makefile中的变量其实就是 C/C++中的宏。如果你要让通配符在变量中展开，也就是让objects的值是所有[.o]的文件名的集合，那么，你可以这样：<br><br>objects := $(wildcard *.o)<br><br>这种用法由关键字&#8220;wildcard&#8221;指出，关于Makefile的关键字，我们将在后面讨论。<br><br><br>四、文件搜寻<br><br>在一些大的工程中，有大量的源文件，我们通常的做法是把这许多的源文件分类，并存放在不同的目录中。所以，当make需要去找寻文件的依赖关系时，你可以在文件前加上路径，但最好的方法是把一个路径告诉make，让make在自动去找。<br><br>Makefile文件中的特殊变量&#8220;VPATH&#8221;就是完成这个功能的，如果没有指明这个变量，make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量，那么，make就会在当当前目录找不到的情况下，到所指定的目录中去找寻文件了。<br><br>VPATH = src:../headers<br><br>上面的的定义指定两个目录，&#8220;src&#8221;和&#8220;../headers&#8221;，make会按照这个顺序进行搜索。目录由&#8220;冒号&#8221;分隔。（当然，当前目录永远是最高优先搜索的地方）<br><br>另一个设置文件搜索路径的方法是使用make的&#8220;vpath&#8221;关键字（注意，它是全小写的），这不是变量，这是一个make的关键字，这和上面提到的那个 VPATH变量很类似，但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种：<br><br>1、vpath &lt;pattern&gt; &lt;directories&gt;<br><br>为符合模式&lt;pattern&gt;的文件指定搜索目录&lt;directories&gt;。<br><br>2、vpath &lt;pattern&gt;<br><br>清除符合模式&lt;pattern&gt;的文件的搜索目录。<br><br>3、vpath<br><br>清除所有已被设置好了的文件搜索目录。<br><br>vapth 使用方法中的&lt;pattern&gt;需要包含&#8220;%&#8221;字符。&#8220;%&#8221;的意思是匹配零或若干字符，例如，&#8220;%.h&#8221;表示所有以&#8220;.h&#8221;结尾的文件。 &lt;pattern&gt;指定了要搜索的文件集，而&lt;directories&gt;则指定了&lt;pattern&gt;的文件集的搜索的目录。例如：<br><br>vpath %.h ../headers<br><br>该语句表示，要求make在&#8220;../headers&#8221;目录下搜索所有以&#8220;.h&#8221;结尾的文件。（如果某文件在当前目录没有找到的话）<br><br>我们可以连续地使用vpath语句，以指定不同搜索策略。如果连续的vpath语句中出现了相同的&lt;pattern&gt;，或是被重复了的&lt;pattern&gt;，那么，make会按照vpath语句的先后顺序来执行搜索。如：<br><br>vpath %.c foo<br>vpath % blish<br>vpath %.c bar<br><br>其表示&#8220;.c&#8221;结尾的文件，先在&#8220;foo&#8221;目录，然后是&#8220;blish&#8221;，最后是&#8220;bar&#8221;目录。<br><br>vpath %.c foo:bar<br>vpath % blish<br><br>而上面的语句则表示&#8220;.c&#8221;结尾的文件，先在&#8220;foo&#8221;目录，然后是&#8220;bar&#8221;目录，最后才是&#8220;blish&#8221;目录。<br><br><br>五、伪目标<br><br>最早先的一个例子中，我们提到过一个&#8220;clean&#8221;的目标，这是一个&#8220;伪目标&#8221;，<br><br>clean:<br>rm *.o temp<br><br>正像我们前面例子中的&#8220;clean&#8221;一样，即然我们生成了许多文件编译文件，我们也应该提供一个清除它们的&#8220;目标&#8221;以备完整地重编译而用。 （以&#8220;make clean&#8221;来使用该目标）<br><br>因为，我们并不生成&#8220;clean&#8221;这个文件。&#8220;伪目标&#8221;并不是一个文件，只是一个标签，由于&#8220;伪目标&#8221;不是文件，所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个&#8220;目标&#8221;才能让其生效。当然，&#8220;伪目标&#8221;的取名不能和文件名重名，不然其就失去了&#8220;伪目标&#8221;的意义了。<br><br>当然，为了避免和文件重名的这种情况，我们可以使用一个特殊的标记&#8220;.PHONY&#8221;来显示地指明一个目标是&#8220;伪目标&#8221;，向make说明，不管是否有这个文件，这个目标就是&#8220;伪目标&#8221;。<br><br>.PHONY : clean<br><br>只要有这个声明，不管是否有&#8220;clean&#8221;文件，要运行&#8220;clean&#8221;这个目标，只有&#8220;make clean&#8221;这样。于是整个过程可以这样写：<br><br>.PHONY: clean<br>clean:<br>rm *.o temp<br><br>伪目标一般没有依赖的文件。但是，我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为&#8220;默认目标&#8221;，只要将其放在第一个。一个示例就是，如果你的 Makefile需要一口气生成若干个可执行文件，但你只想简单地敲一个make完事，并且，所有的目标文件都写在一个Makefile中，那么你可以使用&#8220;伪目标&#8221;这个特性：<br><br>all : prog1 prog2 prog3<br>.PHONY : all<br><br>prog1 : prog1.o utils.o<br>cc -o prog1 prog1.o utils.o<br><br>prog2 : prog2.o<br>cc -o prog2 prog2.o<br><br>prog3 : prog3.o sort.o utils.o<br>cc -o prog3 prog3.o sort.o utils.o<br><br>我们知道，Makefile中的第一个目标会被作为其默认目标。我们声明了一个&#8220;all&#8221;的伪目标，其依赖于其它三个目标。由于伪目标的特性是，总是被执行的，所以其依赖的那三个目标就总是不如&#8220;all&#8221;这个目标新。所以，其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。 &#8220;.PHONY : all&#8221;声明了&#8220;all&#8221;这个目标为&#8220;伪目标&#8221;。<br><br>随便提一句，从上面的例子我们可以看出，目标也可以成为依赖。所以，伪目标同样也可成为依赖。看下面的例子：<br><br>.PHONY: cleanall cleanobj cleandiff<br><br>cleanall : cleanobj cleandiff<br>rm program<br><br>cleanobj :<br>rm *.o<br><br>cleandiff :<br>rm *.diff<br><br>&#8220;make clean&#8221;将清除所有要被清除的文件。&#8220;cleanobj&#8221;和&#8220;cleandiff&#8221;这两个伪目标有点像&#8220;子程序&#8221;的意思。我们可以输入&#8220;make cleanall&#8221;和&#8220;make cleanobj&#8221;和&#8220;make cleandiff&#8221;命令来达到清除不同种类文件的目的。<br><br>六、多目标<br><br>Makefile 的规则中的目标可以不止一个，其支持多目标，有可能我们的多个目标同时依赖于一个文件，并且其生成的命令大体类似。于是我们就能把其合并起来。当然，多个目标的生成规则的执行命令是同一个，这可能会可我们带来麻烦，不过好在我们的可以使用一个自动化变量&#8220;$@&#8221;（关于自动化变量，将在后面讲述），这个变量表示着目前规则中所有的目标的集合，这样说可能很抽象，还是看一个例子吧。<br><br>bigoutput littleoutput : text.g<br>generate text.g -$(subst output,,$@) &gt; $@<br><br>上述规则等价于：<br><br>bigoutput : text.g<br>generate text.g -big &gt; bigoutput<br>littleoutput : text.g<br>generate text.g -little &gt; littleoutput<br><br>其中，-$(subst output,,$@)中的&#8220;$&#8221;表示执行一个Makefile的函数，函数名为subst，后面的为参数。关于函数，将在后面讲述。这里的这个函数是截取字符串的意思，&#8220;$@&#8221;表示目标的集合，就像一个数组，&#8220;$@&#8221;依次取出目标，并执于命令。<br><br><br>七、静态模式<br><br>静态模式可以更加容易地定义多目标的规则，可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法：<br><br>&lt;targets ...&gt;: &lt;target-pattern&gt;: &lt;prereq-patterns ...&gt;<br>&lt;commands&gt;<br>...<br><br><br>targets定义了一系列的目标文件，可以有通配符。是目标的一个集合。<br><br>target-parrtern是指明了targets的模式，也就是的目标集模式。<br><br>prereq-parrterns是目标的依赖模式，它对target-parrtern形成的模式再进行一次依赖目标的定义。<br><br>这样描述这三个东西，可能还是没有说清楚，还是举个例子来说明一下吧。如果我们的&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]这个结尾，形成的新集合。<br><br>所以，我们的&#8220;目标模式&#8221;或是&#8220;依赖模式&#8221;中都应该有&#8220;%&#8221;这个字符，如果你的文件名中有&#8220;%&#8221;那么你可以使用反斜杠&#8220;\&#8221;进行转义，来标明真实的&#8220;%&#8221;字符。<br><br>看一个例子：<br><br>objects = foo.o bar.o<br><br>all: $(objects)<br><br>$(objects): %.o: %.c<br>$(CC) -c $(CFLAGS) $&lt; -o $@<br><br><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;）。于是，上面的规则展开后等价于下面的规则：<br><br>foo.o : foo.c<br>$(CC) -c $(CFLAGS) foo.c -o foo.o<br>bar.o : bar.c<br>$(CC) -c $(CFLAGS) bar.c -o bar.o<br><br>试想，如果我们的&#8220;%.o&#8221;有几百个，那种我们只要用这种很简单的&#8220;静态模式规则&#8221;就可以写完一堆规则，实在是太有效率了。&#8220;静态模式规则&#8221;的用法很灵活，如果用得好，那会一个很强大的功能。再看一个例子：<br><br><br>files = foo.elc bar.o lose.o<br><br>$(filter %.o,$(files)): %.o: %.c<br>$(CC) -c $(CFLAGS) $&lt; -o $@<br>$(filter %.elc,$(files)): %.elc: %.el<br>emacs -f batch-byte-compile $&lt;<br><br><br>$(filter %.o,$(files))表示调用Makefile的filter函数，过滤&#8220;$filter&#8221;集，只要其中模式为&#8220;%.o&#8221;的内容。其的它内容，我就不用多说了吧。这个例字展示了Makefile中更大的弹性。<br><br><br>八、自动生成依赖性<br><br>在Makefile中，我们的依赖关系可能会需要包含一系列的头文件，比如，如果我们的main.c中有一句&#8220;＃i nclude "defs.h"&#8221;，那么我们的依赖关系应该是：<br><br>main.o : main.c defs.h<br><br>但是，如果是一个比较大型的工程，你必需清楚哪些C文件包含了哪些头文件，并且，你在加入或删除头文件时，也需要小心地修改Makefile，这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情，我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个&#8220;-M&#8221;的选项，即自动找寻源文件中包含的头文件，并生成一个依赖关系。例如，如果我们执行下面的命令：<br><br>cc -M main.c<br><br>其输出是：<br><br>main.o : main.c defs.h<br><br>于是由编译器自动生成的依赖关系，这样一来，你就不必再手动书写若干文件的依赖关系，而由编译器自动生成了。需要提醒一句的是，如果你使用GNU的C/C++编译器，你得用&#8220;-MM&#8221;参数，不然，&#8220;-M&#8221;参数会把一些标准库的头文件也包含进来。<br><br>gcc -M main.c的输出是：<br><br>main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \<br>/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \<br>/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \<br>/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \<br>/usr/include/bits/sched.h /usr/include/libio.h \<br>/usr/include/_G_config.h /usr/include/wchar.h \<br>/usr/include/bits/wchar.h /usr/include/gconv.h \<br>/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \<br>/usr/include/bits/stdio_lim.h<br><br><br>gcc -MM main.c的输出则是：<br><br>main.o: main.c defs.h<br><br>那么，编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来，我们的Makefile也要根据这些源文件重新生成，让Makefile 自已依赖于源文件？这个功能并不现实，不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中，为每一个&#8220;name.c&#8221;的文件都生成一个&#8220;name.d&#8221;的Makefile文件，[.d]文件中就存放对应[.c]文件的依赖关系。<br><br>于是，我们可以写出[.c]文件和[.d]文件的依赖关系，并让make自动更新或自成[.d]文件，并把其包含在我们的主Makefile中，这样，我们就可以自动化地生成每个文件的依赖关系了。<br><br>这里，我们给出了一个模式规则来产生[.d]文件：<br><br>%.d: %.c<br>@set -e; rm -f $@; \<br>$(CC) -M $(CPPFLAGS) $&lt; &gt; $@.$$$$; \<br>sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' &lt; $@.$$$$ &gt; $@; \<br>rm -f $@.$$$$<br><br><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命令的用法请参看相关的使用文档。第四行就是删除临时文件。<br><br>总而言之，这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖，即把依赖关系：<br><br>main.o : main.c defs.h<br><br>转成：<br><br>main.o main.d : main.c defs.h<br><br>于是，我们的[.d]文件也会自动更新了，并会自动生成了，当然，你还可以在这个[.d]文件中加入的不只是依赖关系，包括生成的命令也可一并加入，让每个 [.d]文件都包含一个完赖的规则。一旦我们完成这个工作，接下来，我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用 Makefile的&#8220;include&#8221;命令，来引入别的Makefile文件（前面讲过），例如：<br><br>sources = foo.c bar.c<br><br>include $(sources:.c=.d)<br><br>上述语句中的&#8220;$(sources:.c=.d)&#8221;中的&#8220;.c=.d&#8221;的意思是做一个替换，把变量$(sources)所有[.c]的字串都替换成 [.d]，关于这个&#8220;替换&#8221;的内容，在后面我会有更为详细的讲述。当然，你得注意次序，因为include是按次来载入文件，最先载入的[.d]文件中的目标会成为默认目标。</font></font></p>
<p><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><br></font></font></p>
<p><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font face="verdana, arial, helvetica"><font size=2>书写命令<br>————<br><br>每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令，每条命令的开头必须以[Tab]键开头，除非，命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略，但是如果该空格或空行是以Tab键开头的，那么make会认为其是一个空命令。<br><br>我们在UNIX下可能会使用不同的Shell，但是make的命令默认是被&#8220;/bin/sh&#8221;——UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中，&#8220;#&#8221;是注释符，很像C/C++中的&#8220;//&#8221;，其后的本行字符都被注释。<br><br>一、显示命令<br><br>通常，make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用&#8220;@&#8221;字符在命令行前，那么，这个命令将不被make显示出来，最具代表性的例子是，我们用这个功能来像屏幕显示一些信息。如：<br><br>@echo 正在编译XXX模块......<br><br>当make执行时，会输出&#8220;正在编译XXX模块......&#8221;字串，但不会输出命令，如果没有&#8220;@&#8221;，那么，make将输出：<br><br>echo 正在编译XXX模块......<br>正在编译XXX模块......<br><br>如果make执行时，带入make参数&#8220;-n&#8221;或&#8220;--just-print&#8221;，那么其只是显示命令，但不会执行命令，这个功能很有利于我们调试我们的Makefile，看看我们书写的命令是执行起来是什么样子的或是什么顺序的。<br><br>而make参数&#8220;-s&#8221;或&#8220;--slient&#8221;则是全面禁止命令的显示。<br><br><br><br>二、命令执行<br><br>当依赖目标新于目标时，也就是当规则的目标需要被更新时，make会一条一条的执行其后的命令。需要注意的是，如果你要让上一条命令的结果应用在下一条命令时，你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令，你希望第二条命令得在cd之后的基础上运行，那么你就不能把这两条命令写在两行上，而应该把这两条命令写在一行上，用分号分隔。如：<br><br>示例一：<br>exec:<br>cd /home/hchen<br>pwd<br><br>示例二：<br>exec:<br>cd /home/hchen; pwd<br><br>当我们执行&#8220;make exec&#8221;时，第一个例子中的cd没有作用，pwd会打印出当前的Makefile目录，而第二个例子中，cd就起作用了，pwd会打印出&#8220;/home/hchen&#8221;。<br><br>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;等后缀。<br><br><br><br>三、命令出错<br><br>每当命令运行完后，make会检测每个命令的返回码，如果命令返回成功，那么make会执行下一条命令，当规则中所有的命令成功返回后，这个规则就算是成功完成了。如果一个规则中的某个命令出错了（命令退出码非零），那么make就会终止执行当前规则，这将有可能终止所有规则的执行。<br><br>有些时候，命令的出错并不表示就是错误的。例如mkdir命令，我们一定需要建立一个目录，如果目录不存在，那么mkdir就成功执行，万事大吉，如果目录存在，那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录，于是我们就不希望mkdir出错而终止规则的运行。<br><br>为了做到这一点，忽略命令的出错，我们可以在Makefile的命令行前加一个减号&#8220;-&#8221;（在Tab键之后），标记为不管命令出不出错都认为是成功的。如：<br><br>clean:<br>-rm -f *.o<br><br>还有一个全局的办法是，给make加上&#8220;-i&#8221;或是&#8220;--ignore-errors&#8221;参数，那么，Makefile中所有命令都会忽略错误。而如果一个规则是以&#8220;.IGNORE&#8221;作为目标的，那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法，你可以根据你的不同喜欢设置。<br><br>还有一个要提一下的make的参数的是&#8220;-k&#8221;或是&#8220;--keep-going&#8221;，这个参数的意思是，如果某规则中的命令出错了，那么就终目该规则的执行，但继续执行其它规则。<br><br><br><br>四、嵌套执行make<br><br>在一些大的工程中，我们会把我们不同模块或是不同功能的源文件放在不同的目录中，我们可以在每个目录中都书写一个该目录的Makefile，这有利于让我们的Makefile变得更加地简洁，而不至于把所有的东西全部写在一个Makefile中，这样会很难维护我们的Makefile，这个技术对于我们模块编译和分段编译有着非常大的好处。<br><br>例如，我们有一个子目录叫subdir，这个目录下有个Makefile文件，来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写：<br><br>subsystem:<br>cd subdir &amp;&amp; $(MAKE)<br><br>其等价于：<br><br>subsystem:<br>$(MAKE) -C subdir<br><br>定义$(MAKE)宏变量的意思是，也许我们的make需要一些参数，所以定义成一个变量比较利于维护。这两个例子的意思都是先进入&#8220;subdir&#8221;目录，然后执行make命令。<br><br>我们把这个Makefile叫做&#8220;总控Makefile&#8221;，总控Makefile的变量可以传递到下级的Makefile中（如果你显示的声明），但是不会覆盖下层的Makefile中所定义的变量，除非指定了&#8220;-e&#8221;参数。<br><br>如果你要传递变量到下级Makefile中，那么你可以使用这样的声明：<br><br>export &lt;variable ...&gt;<br><br>如果你不想让某些变量传递到下级Makefile中，那么你可以这样声明： <br><br>unexport &lt;variable ...&gt;<br><br>如：<br><br>示例一：<br><br>export variable = value<br><br>其等价于：<br><br>variable = value<br>export variable<br><br>其等价于：<br><br>export variable := value<br><br>其等价于：<br><br>variable := value<br>export variable<br><br>示例二：<br><br>export variable += value<br><br>其等价于：<br><br>variable += value<br>export variable<br><br>如果你要传递所有的变量，那么，只要一个export就行了。后面什么也不用跟，表示传递所有的变量。<br><br>需要注意的是，有两个变量，一个是SHELL，一个是MAKEFLAGS，这两个变量不管你是否export，其总是要传递到下层Makefile中，特别是MAKEFILES变量，其中包含了make的参数信息，如果我们执行&#8220;总控Makefile&#8221;时有make参数或是在上层Makefile中定义了这个变量，那么MAKEFILES变量将会是这些参数，并会传递到下层Makefile中，这是一个系统级的环境变量。<br><br>但是make命令中的有几个参数并不往下传递，它们是&#8220;-C&#8221;,&#8220;-f&#8221;,&#8220;-h&#8221;&#8220;-o&#8221;和&#8220;-W&#8221;（有关Makefile参数的细节将在后面说明），如果你不想往下层传递参数，那么，你可以这样来：<br><br>subsystem:<br>cd subdir &amp;&amp; $(MAKE) MAKEFLAGS=<br><br>如果你定义了环境变量MAKEFLAGS，那么你得确信其中的选项是大家都会用到的，如果其中有&#8220;-t&#8221;,&#8220;-n&#8221;,和&#8220;-q&#8221;参数，那么将会有让你意想不到的结果，或许会让你异常地恐慌。<br><br>还有一个在&#8220;嵌套执行&#8221;中比较有用的参数，&#8220;-w&#8221;或是&#8220;--print-directory&#8221;会在make的过程中输出一些信息，让你看到目前的工作目录。比如，如果我们的下级make目录是&#8220;/home/hchen/gnu/make&#8221;，如果我们使用&#8220;make -w&#8221;来执行，那么当进入该目录时，我们会看到：<br><br>make: Entering directory `/home/hchen/gnu/make'.<br><br>而在完成下层make后离开目录时，我们会看到：<br><br>make: Leaving directory `/home/hchen/gnu/make'<br><br>当你使用&#8220;-C&#8221;参数来指定make下层Makefile时，&#8220;-w&#8221;会被自动打开的。如果参数中有&#8220;-s&#8221;（&#8220;--slient&#8221;）或是&#8220;--no-print-directory&#8221;，那么，&#8220;-w&#8221;总是失效的。<br><br><br><br>五、定义命令包<br><br>如果Makefile中出现一些相同命令序列，那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以&#8220;define&#8221;开始，以&#8220;endef&#8221;结束，如：<br><br>define run-yacc<br>yacc $(firstword $^)<br>mv y.tab.c $@<br>endef<br><br>这里，&#8220;run-yacc&#8221;是这个命令包的名字，其不要和Makefile中的变量重名。在&#8220;define&#8221;和&#8220;endef&#8221;中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序，因为Yacc程序总是生成&#8220;y.tab.c&#8221;的文件，所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。<br><br>foo.c : foo.y<br>$(run-yacc)<br><br>我们可以看见，要使用这个命令包，我们就好像使用变量一样。在这个命令包的使用中，命令包&#8220;run-yacc&#8221;中的&#8220;$^&#8221;就是&#8220;foo.y&#8221;，&#8220;$@&#8221;就是&#8220;foo.c&#8221;（有关这种以 &#8220;$&#8221;开头的特殊变量，我们会在后面介绍），make在执行命令包时，命令包中的每个命令会被依次独立执行。<br></font></font></font></font></p>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59812.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-07-03 16:49 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/07/03/59812.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>我和同居女友不得不说的故事</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/06/10/59202.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Wed, 10 Jun 2009 04:45:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/06/10/59202.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59202.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/06/10/59202.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59202.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59202.html</trackback:ping><description><![CDATA[徒步从化<br>同居女友邀我去旅游，我犹豫了好久，看着报名单上一大串的MM一，MM二<img height=20 src="http://www.cnitblog.com/Emoticons/QQ/03.gif" width=20 border=0>，终于还是答应了。<img height=20 src="http://www.cnitblog.com/Emoticons/QQ/14.gif" width=20 border=0>主要是需要起得太早，回来得太晚，还要牺牲我的篮球。<br>有时候生物钟好奇怪，闹钟没有响，我却提前半小时醒了。到了深大，等了半个多小时，上了车，发现人没有报名的多，除了司机，刚好9男9女。<br>12点到从化，下车，走路。<br>开始是乡间小路，两旁是水稻，然后是河流，树林，竹林，李子林，村庄，荔枝林.........<br>天黑时走完全程，分别有一GG，一MM扭到了脚，同居女友好象途中遇到亲威来访，不过大家都走到了最后，没有掉队的，这是最值得庆贺的事了。<br>在从化吃了简易大餐，再坐了三个小时车，回到了深圳。到家，快1点。<br>车上有人说不洗澡就去睡觉，可能吗？<img height=20 src="http://www.cnitblog.com/Emoticons/QQ/laf.gif" width=20 border=0><br><br>一路上没什么风景，只需赶路，所以没走多远，已经听到两个人说后悔了。比起云南的原始森林，这差远了。不过我倒是不介意，只是想体会一下长途步行的感觉。上一次走这么远，还是10几年前的事情：一群同学从晚上走到天亮，爬上了峰山，看日出。那时的感觉真好。这路上有许多李子，大部分可以随便摘了吃；只是荔枝却奇怪的还没有成熟，都还是青色的籽，也许他们一辈子也成熟不了。<br>等同伴把照片给了我，我放几张上来。<br>徒步？还是漂流的好<img height=20 src="http://www.cnitblog.com/Emoticons/QQ/shifty.gif" width=20 border=0><img height=20 src="http://www.cnitblog.com/Emoticons/QQ/25.gif" width=20 border=0> 
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59202.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-06-10 12:45 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/06/10/59202.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于中间件的辩论，挺有意思的</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59024.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Wed, 03 Jun 2009 10:12:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59024.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59024.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59024.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59024.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59024.html</trackback:ping><description><![CDATA[那个论坛不让复制，希望你看到此文的时候，它在健在： <br>http://bbs.dvbcn.com/showtopic-40536.html<br>个人认为：有个概念这个辩论的人还没有搞清楚----什么是中间件（这里指数字电视中间件）。<br><br>定义如下：数字电视中间件是指<strong>位于数字电视机顶盒内部实时操作系统与应用程序之间</strong>的软件部分，它<strong>以应用程序接口API的形式存在</strong>，整个API集合被存储在机顶盒的闪存FLASH中。中间件技术是一个纯软件系统技术，它也是一个比较笼统的概念。一般地说，它是建立在数字电视接收设备驱动层之上，为交互应用提供一个完整的应用编程接口的软件系统。它包含一系列的功能，如内存管理、通信管理、图形系统管理、SI数据装载、系统资源管理以及与前端系统间的通信及控制等。&nbsp; <br><br>按照这个概念来看，现在就没有哪个厂商在做中间件。比较接近的，有CA、BROWSER和VOD。不过BROWSER和VOD它们都算是应用软件了，处于最上层，并不是API。CA勉强算，起码界面还是应用程序来控制。更别说现在某些公司推出的所谓中间件，把机顶盒所有功能都做完了，不留下任何应用开发的余地，完全跟微软捆挷IE一样的。坚决抵制！<br><br>真正的中间件，只有机顶盒厂商自己在做，而且是坚决不开放，坚决不标准的！<img src="http://www.cnitblog.com/CuteSoft_Client/CuteEditor/images/emsmiled.gif" align=absMiddle border=0><br><br>中间件其实是个好东西！
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59024.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-06-03 18:12 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59024.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于数字电视中间件的不同观点----转</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59023.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Wed, 03 Jun 2009 09:49:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59023.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59023.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59023.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59023.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59023.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 感觉观点说得比较实在，观点二就光是大话。&nbsp;&nbsp;<a href='http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59023.html'>阅读全文</a><img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59023.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-06-03 17:49 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59023.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>数字电视中间件标准之MPH----转载</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59022.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Wed, 03 Jun 2009 09:44:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59022.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59022.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59022.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59022.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59022.html</trackback:ping><description><![CDATA[<p><span><span>数字电视中间件是指位于数字电视机顶<span>盒内部</span>实时操作系统与应用程序之间的软件部分，它以应用程序接口</span></span><span><span>API</span></span><span><span>的形式存在，整个</span></span><span><span>API</span></span><span><span>集合被存储在机顶盒的闪存</span></span><span><span>FLASH</span></span><span><span>中。针对机顶盒的应用程序基于</span></span><span><span>API</span></span><span><span>进行开发，能够支持丰富的应用。采用中间件系统，可以跨越技术、标准等复杂的内容，用简单的方法定制具有自己特色的应用软件，从而在提高开发效率、减少开发成本的同时能够跟上技术的发展，将应用的开发变得更加简捷，使产品的开放性和可移植性更强。虽然中间件对机顶盒硬件资源的要求较高，但在开发大量应用<span>时成本</span>大大下降。</span></span><span><span> </span></span><span><br><br><strong><span>1</span></strong></span><strong><span>　数字电视的中间件技术</span></strong><span><span> </span></span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>中间件技术是一个纯软件系统技术，它也是一个比较笼统的概念。一般地说，它是建立在数字电视接收设备驱动层之上，为交互应用提供一个完整的应用编程接口的软件系统。它包含一系列的功能，如内存管理、通信管理、图形系统管理、</span></span><span><span>SI</span></span><span><span>数据装载、系统资源管理以及与前端系统间的通信及控制等。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>目前，比较成熟的商用中间件产品有</span></span><span><span><span>OpenTv</span></span></span><span><span>的</span></span><span><span>EN2</span></span><span><span>、</span></span><span><span>Liberate</span></span><span><span>的</span></span><span><span>TV Navigator for DTV</span></span><span><span>、</span></span><span><span><span>Enreach</span></span></span><span><span>的</span></span><span><span><span>EnreachTV</span></span></span><span><span> for DTV</span></span><span><span>、</span></span><span><span>Canal Plus</span></span><span><span>的</span></span><span><span><span>Mediahighway</span></span></span><span><span>以及</span></span><span><span>NDS</span></span><span><span>的</span></span><span><span>NDS Core</span></span><span><span>等。目前，在我国已经有一定市场份额的中间件生产厂商主要有</span></span><span><span>Canal Plus</span></span><span><span>、</span></span><span><span>NDS</span></span><span><span>和</span></span><span><span><span>OpenTv</span></span></span><span><span>等几家。法国</span></span><span><span>Canal Plus</span></span><span><span>的</span></span><span><span><span>Mediahighway</span></span></span><span><span>是欧洲中间件系统的代表，其最早采用的编程语言是一种解释执行的私有语言。后来，</span></span><span><span>Canal Plus</span></span><span><span>采用</span></span><span><span>Java</span></span><span><span>语言和标准的数据下载协议</span></span><span><span>DSMCC Object Carousel(</span></span><span><span>即</span></span><span><span>DSMCC</span></span><span><span>对象轮盘传输</span></span><span><span>)</span></span><span><span>，重新进行系统设计和制定编程接口，成功地将</span></span><span><span>Java </span></span><span><span>引入数字电视机顶盒中，</span></span><span><span>Canal Plus</span></span><span><span>的中间件产品在欧洲有广阔的市场。英国的</span></span><span><span>NDS</span></span><span><span>的中间<span>件解决</span>方案主要是基于</span></span><span><span>HTML</span></span><span><span>，利用</span></span><span><span>HTML</span></span><span><span>网页浏览器也能实现一定的互动性。目前，</span></span><span><span>NDS</span></span><span><span>也正在研究基于</span></span><span><span>Java</span></span><span><span>的解决方案。</span></span><span><span><span>OpenTV</span></span></span><span><span>是国际上极负盛名的中间件生产厂商，它采用的编程语言是</span></span><span><span>C</span></span><span><span>，也是解释执行的。</span></span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>不同的中间件系统会提供不同的与下层驱动资源模块接口以及与上层应用编程接口，因此不同的系统之间是不能互通的。因而人们在数字电视业务的不断拓展过程中，认识到了制定统一的应用程序接口的重要性。可以说，中间件技术的发展与中间件标准的制定进程是同步的。数字电视领域的两大标准化组织欧洲的</span></span><span><span>DVB</span></span><span><span>和美国的</span></span><span><span>ATSC</span></span><span><span>在制订数字电视广播标准、数据广播和交互业务标准之后，开始制定中间件的标准。</span></span><span><span>DVB</span></span><span><span>已经提出了基于</span></span><span><span>Java</span></span><span><span>虚拟机的中间件标准</span></span><span><span>—</span></span><span><span>多媒体家庭平台（</span></span><span><span>MHP</span></span><span><span>），我国的数字电视中间件标准主要参考</span></span><span><span>MHP</span></span><span><span>，目前正在制定之中。</span></span><span><span>ATSC</span></span><span><span>成立了</span></span><span><span>T3/S17</span></span><span><span>技术专家小组委员会，致力于机顶盒软件环境的定义，称之为数字电视应用软件环境（</span></span><span><span>DASE</span></span><span><span>）。下面就对</span></span><span><span>MHP</span></span><span><span>和</span></span><span><span>DASE</span></span><span><span>做简要介绍。</span></span></p>
<p>&nbsp;</p>
<p><strong><span>2</span></strong><strong><span>、欧洲的</span></strong><strong><span>MHP</span></strong></p>
<p><span><span>由于</span></span><span><span>DVB</span></span><span><span>采取市场驱动的框架，因此初期的工作重点是广播基础设施，在大量的数字广播传输标准取得成功后，</span></span><span><span>DVB</span></span><span><span>的工作重点转移到交互业务这一层次，代表成果是数据广播标准和交互业务标准。由于看到广播、计算机和消费电子在家庭中的聚合，</span></span><span><span>DVB</span></span><span><span>又迈出了第三步，即建立多媒体家庭平台</span></span><span><span>(Multimedia Home Platform</span></span><span><span>，</span></span><span><span>MHP)</span></span><span><span>标准，按照</span></span><span><span>MHP</span></span><span><span>组主席</span></span><span><span><span>Georg</span></span></span><span><span> <span>Luetteke</span></span></span><span><span>博士的说法，</span></span><span><span>MHP</span></span><span><span>所带来的数字电视和相关多媒体业务的融合是</span></span><span><span>WWW</span></span><span><span>出现以来最大的技术奇迹，也是现在能够想象到的广播技术的终极。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp; MHP</span></span><span><span>有关的工作开始于</span></span><span><span>1997</span></span><span><span>年初，工作模式遵循</span></span><span><span>DVB</span></span><span><span>的工作原则，即先研究市场需求，再进行技术标准开发。</span></span><span><span>MHP</span></span><span><span>作为</span></span><span><span>DVB</span></span><span><span>商业部（</span></span><span><span>CM</span></span><span><span>）的一个工作组，由</span></span><span><span>DVB</span></span><span><span>指导委员会（</span></span><span><span>SB</span></span><span><span>）于</span></span><span><span>1997</span></span><span><span>年</span></span><span><span>12</span></span><span><span>月正式批准成立，其任务是调查多媒体家庭平台的商业需求。其后，在</span></span><span><span>DVB</span></span><span><span>技术部成立了</span></span><span><span>MHP</span></span><span><span>技术面工作组</span></span><span><span>TAM</span></span><span><span>（</span></span><span><span>Technical Aspects of MHP</span></span><span><span>），其任务是编写</span></span><span><span>MHP</span></span><span><span>技术规范。与此同时，</span></span><span><span>MHP</span></span><span><span>组仍在进行</span></span><span><span>MHP</span></span><span><span>商业需求的细化和辨别工作，两个组相互协作，联系密切，共同完成多媒体家庭平台这项牵涉面广的复杂工作。</span></span><span><br><br><span>&nbsp;&nbsp;&nbsp; MHP API</span></span><span><span>的最早竞争者有三个：来自</span></span><span><span>Sun Microsystems</span></span><span><span>和</span></span><span><span>Thomson Multimedia</span></span><span><span>的</span></span><span><span><span>OpenTV</span></span></span><span><span>，来自数字视听工业论坛</span></span><span><span>DAVIC(Digital Audio Video Industries Council)</span></span><span><span>的</span></span><span><span>MHEG</span></span><span><span>5/MHEG6 </span></span><span><span>＋</span></span><span><span> Java</span></span><span><span>提案和来自法国地面数字电视广播商</span></span><span><span>Canal Plus</span></span><span><span>的</span></span><span><span>Multimedia Highway</span></span><span><span>。不久，</span></span><span><span> Multimedia Highway</span></span><span><span>就提出愿意采取一定策略来容纳</span></span><span><span>MHEG</span></span><span><span>5</span></span><span><span>和</span></span><span><span>Java</span></span><span><span>。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span><span>从</span></span><span><span>1998</span></span><span><span>年到现在，</span></span><span><span>DVB</span></span><span><span>内部关于</span></span><span><span>MHP</span></span><span><span>的辩论十分激烈，原因是</span></span><span><span>DVB</span></span><span><span>各成员的想法各不相同，而这样的一个标准涉及到欧洲的情况不同的多个国家。争论的核心主要集中在六个方面：开放标准还是封闭标准；增强电视还是交互电视；</span></span><span><span>Internet</span></span><span><span>的中心地位问题；欧洲和美国的不同；继承还是创新；知识产权问题。其中对增强电视的情有独钟、难以达成一致意见和知识产权问题是阻碍标准制定的三个主要障碍。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span><span>尽管争论激烈，甚至有人怀疑</span></span><span><span>MHP</span></span><span><span>这样的统一标准是否必要，即使制定了能否实现，但</span></span><span><span>DVB</span></span><span><span>的</span></span><span><span>MHP</span></span><span><span>项目还是取得了不少成果，目前达成一致的是定义一套兼容</span></span><span><span>Java</span></span><span><span>的应用编程接口（</span></span><span><span>API</span></span><span><span>）。此</span></span><span><span>API</span></span><span><span>以三个类别</span></span><span><span>(profile)</span></span><span><span>的形式定义，或者说三个层次，其中每个上层类别都包含下层类别。第一个类别是增强电视类别（</span></span><span><span>Enhanced TV profile</span></span><span><span>），它是基于</span></span><span><span>Java</span></span><span><span>的，而且其中不包括</span></span><span><span>HTML</span></span><span><span>兼容。第二个类别称为带反向通道的交互电视，定义为两种可选格式：一种是</span></span><span><span>&#8220;</span></span><span><span>纯</span></span><span><span>Java&#8221;</span></span><span><span>方案，源自</span></span><span><span>Sun</span></span><span><span>的</span></span><span><span><span>PersonalJava</span></span></span><span><span>规范和</span></span><span><span>/</span></span><span><span>或</span></span><span><span><span>JavaTV</span></span></span><span><span>规范，这种方案定义为缺省类别，另一种可选的格式是在缺省类别的基础上增添兼容</span></span><span><span>HTML</span></span><span><span>。第三个类别称为</span></span><span><span>Internet</span></span><span><span>接入，在交互电视类别的基础上增加</span></span><span><span>IP</span></span><span><span>和兼容</span></span><span><span>HTML</span></span><span><span>。</span></span><span><br><br><span>&nbsp;&nbsp;&nbsp; API</span></span><span><span>的目的是通过互操作性提供统一的应用平台，但上述基于</span></span><span><span>Java</span></span><span><span>的</span></span><span><span>API</span></span><span><span>方案并非如此，尤其是在兼容</span></span><span><span>HTML</span></span><span><span>方面模棱两可的规定使得互操作性大打折扣。最热心兼容</span></span><span><span>HTML</span></span><span><span>的是有线电视运营商，他们关心快速</span></span><span><span>Internet</span></span><span><span>接入能够成为自己的一项关键业务，而其竞争对手－卫星运营商和数字地面电视广播公司－对兼容</span></span><span><span>HTML</span></span><span><span>并不热心，因此，</span></span><span><span>MHP</span></span><span><span>交互电视类别的两种选项实际上在有线电视和卫星与地面广播之间埋下了一道潜在裂痕。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>欧洲的统一</span></span><span><span>API</span></span><span><span>分三步走，不是技术问题，而是考虑到欧盟成员国在政策方面的差异。而政策差异是其社会文化环境差异的反映，特别在电视广播领域这种差异更加明显，各成员国联系到现有公共广播制度，在国家层面上会采取不同行动。因此，现行的</span></span><span><span>MHP</span></span><span><span>标准是一个结构冗杂的大系统，它在具有先进性和<span>可</span>扩展性的同时，各个程序包之间并不和谐，存在功能上的叠加和重复。</span></span><span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>3</span></span><span><span>、美国的</span></span><span><span>DASE</span></span></p>
<p><span><span>在</span></span><span><span>ATSC 1983</span></span><span><span>年成立伊始，双向增强电视就是构想之一，但直到</span></span><span><span>1996</span></span><span><span>年</span></span><span><span>FCC</span></span><span><span>大部分采纳</span></span><span><span>ATSC</span></span><span><span>标准时，双向交互电视问题都一直悬而未决，没有人能说清楚呈现在观众面前的交互电视到底是什么样。在此期间，时代华纳曾开展交互电视实验，但没取得成功，相反，交互式的</span></span><span><span>Internet</span></span><span><span>异军突起，取得巨大成功。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>消费电子制造商（包括数字电视芯片制造商）和内容生产商都迫切需要制定统一的接收机软件平台，这是交互电视取得成功的关键。因此，</span></span><span><span>1997</span></span><span><span>年末，</span></span><span><span>ATSC</span></span><span><span>成立了数字电视应用软件环境（</span></span><span><span>DASE</span></span><span><span>）特别工作组，它源于</span></span><span><span>ATSC T3/S16</span></span><span><span>（交互业务）专家组内的一个小组，后来独立成一个特别工作组，并很快成为</span></span><span><span>T3</span></span><span><span>的直属专家组，编号为</span></span><span><span>T3/S17</span></span><span><span>。</span></span><span><span>S17</span></span><span><span>之下又细分为多个小组：系统业务组、总体组、类别</span></span><span><span>(profile)</span></span><span><span>组、安全组、呈现引擎组，随着工作的深入，小组的划分也有所变化。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>作为应用软件环境，</span></span><span><span>DASE</span></span><span><span>包括四个组成部分：应用执行引擎（</span></span><span><span>Application Execution <span>Engine,AEE</span></span></span><span><span>）、呈现引擎（</span></span><span><span>Presentation <span>Engine,PE</span></span></span><span><span>）、内容解码器（</span></span><span><span>Content Decoder</span></span><span><span>）和应用程序接口</span></span><span><span>(API)</span></span><span><span>。这四个部分构成一个有机整体，这一体系结构是随着</span></span><span><span>DASE</span></span><span><span>的工作进展而提出并不断完善的。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>应用执行引擎</span></span><span><span>AEE</span></span><span><span>直接在接收机的操作系统和库之上实现，以平台无关的方式解释或执行应用代码中的程序部分，为内容解码器和呈现引擎提供插件平台，作为底层软硬件平台的抽象，扮演</span></span><span><span>DASE</span></span><span><span>环境集成者的角色，同时负责管理多个应用可能竞争的资源。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>呈现引擎</span></span><span><span>PE</span></span><span><span>的任务是：</span></span><span><span>(1) </span></span><span><span>执行描述屏幕如何显示的（可能是说明性的）代码</span></span><span><span>; (2) </span></span><span><span>提供屏幕的空间布局</span></span><span><span>; (3) </span></span><span><span>提供屏幕上小部件</span></span><span><span>(widget)</span></span><span><span>或对象的时间同步</span></span><span><span>; (4) </span></span><span><span>允许小部件或对象在屏幕上的合成（透明和半透明等）</span></span><span><span>; (5) </span></span><span><span>在屏幕上对已知类属的小部件或对象进行实例化。</span></span><span><span>PE</span></span><span><span>必须对用户产生的事件及其手势或动作<span>作出</span>反应。</span></span><span><span>PE</span></span><span><span>可以是平台有关的，即使用接收机操作系统的私有库，如果制造商允许，可以下载平台无关的</span></span><span><span>PE</span></span><span><span>（只依赖于</span></span><span><span>AEE</span></span><span><span>和</span></span><span><span>API</span></span><span><span>），通过注册可以代替制造商的</span></span><span><span>PE</span></span><span><span>。在</span></span><span><span>DASE</span></span><span><span>接收机中，将只有一个</span></span><span><span>PE</span></span><span><span>。推荐的</span></span><span><span>DASE PE</span></span><span><span>为</span></span><span><span>XHTML</span></span><span><span>。</span></span><span>&nbsp;</span><span><br><br><span>&nbsp;&nbsp;&nbsp; </span></span><span><span>面向系统服务的应用程序接口</span></span><span><span>API</span></span><span><span>是</span></span><span><span>DASE</span></span><span><span>为应用程序提供的接口标准，应用通过它访问下层操作系统和接收机硬件提供的系统服务，是操作系统自有库之上的抽象层，是</span></span><span><span>DASE</span></span><span><span>的核心。</span></span><span></span></p>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59022.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-06-03 17:44 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59022.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>数字电视中间件的基本要求----转载</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59021.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Wed, 03 Jun 2009 09:42:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59021.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/59021.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59021.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/59021.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/59021.html</trackback:ping><description><![CDATA[<br>数字电视中间件的基本要求<br>
<p>目前，深圳<a href="http://www.ent.eetchina.com/SEARCH/ART/%CA%FD%D7%D6%B5%E7%CA%D3.HTM">数字电视</a>开始正式应用中间件。之前，由于数字电视增值业务在全国范围内很少开展，因此，中间件应用很少并只应用在浏览器上。本文特邀请深圳市有线电视运营商<a href="http://www.ent.eetchina.com/SEARCH/ART/%CC%EC%CD%FE%CA%D3%D1%B6.HTM">天威视讯</a>股份有限公司结合深圳数字电视业务开展的实际情况，提出对中间件方案的基本需求，以供中间件厂商参考。 </p>
<p>平移<a href="http://www.ent.eetchina.com/SEARCH/ART/%BB%FA%B6%A5%BA%D0.HTM">机顶盒</a>难以满足后续应用 </p>
<p>自2005年开始，国内有线数字电视开始进入整体转换时代，到目前为止，已有许多城市完成了整体转换工作，有线电视的发展也步入了一个全新的阶段。无论是正在进行&#8220;整转&#8221;还是已经完成&#8220;整转&#8221;的网络运营商，都希望充分利用数字电视平台，为用户提供新的增值业务，在增加用户体验价值的同时，进一步提高赢利能力。因此，在数字电视平台上如何快速有效地部署新业务也就成为运营商必须要面对的一个非常重要的问题，而机顶盒的软件架构是决定新业务能否实现快速部署的一个重要因素。 </p>
<p>在数字电视普及阶段，普遍采用的是开发加集成的机顶盒软件架构，即机顶盒的基础应用，由厂商按照运营商的实际需求开发。而增值应用则由应用提供商提供类库，机顶盒厂商进行集成。其优点在于成本低，程序运行效率高，但是也带来了机顶盒界面不统一，对应用的任何修改都必须对机顶盒进行全网升级，使得运营商客户服务压力增加，新应用的部署周期长，难以适应快速变换的市场需求。 </p>
<p>数字电视中间件为解决上述问题提供了一条途径。基于中间件平台的机顶盒软件架构的优势在于他屏蔽了各硬件平台、操作系统及数据库等之间的差异性，使得基于中间件平台的应用能够实现一次开发，多次使用。对于应用开发商和机顶盒厂商来说降低了软件的开发成本，只需要对应用进行很少的修改，甚至不修改就可以将应用部署在运营商的各种机顶盒之上；对于运营商而言由于不需要关心机顶盒的硬件平台使得运营商可以专注于运行于机顶盒之上的应用，降低了运营商的运营成本和对软件管理的难度，这种优势在运营商网内存在多家机顶盒供应商时尤为明显；对于用户而言由于使用了统一的应用，UI以及操作获得了更佳的使用感受，同时也降低了运营商客服部门的压力。 </p>
<p>运营商提出四大条件 </p>
<p>目前，数字电视产业正在快速发展，国内外已经有不少厂商推出了各自的中间件解决方案，部分产品已经被全球各大卫星、有线运营商广泛采用。那么，运营商如何选择中间件方案才能最大限度地发挥中间件的优势呢，以下结合深圳的运营经验提出选择中间件方案的大致需求以供参考。 </p>
<p>能稳定运行在较低配置的机顶盒上 </p>
<p>整转时代运营商为了降低成本普遍使用了较低配置的机顶盒，典型配置为CPU(中央处理器)&#8804;200MHz，8MB的flash(闪存)， 16MB的RAM(内存)甚至更低，在这样的硬件平台上进行新业务的开发和部署非常受限。但是这样配置的机顶盒在运营商的网内往往有数十万台甚至上百万台，是运营商利润的根本来源。因此，运营商选择的中间件方案必须能够稳定地运行在较低配置的机顶盒上，且具有足够的可扩展性，能够满足未来一段时间内新业务的部署需求，如EPG增值业务、证券业务等。 </p>
<p>软件架构必须模块化 </p>
<p>中国的数字电视产业已经逐步进入快速发展阶段，很多运营商已经推出多款机顶盒供用户选择，为用户提供点播、时移、游戏，甚至高清电视业务，而不同的机顶盒对中间件显然有着不一样的需求。例如标清单向机顶盒只需要最基本的功能即可，交互机顶盒则要求中间件能够支持点播、时移等交互类业务。这就意味着中间件的软件架构必须是模块化的，能够根据运营商的实际需求针对每一款机顶盒灵活地调整中间件的功能集。 </p>
<p>这样一方面降低了机顶盒软件的复杂程度，缩短了机顶盒的开发周期，另一方面也节省了资源，使得运营商能够充分利用机顶盒的系统资源，提供更多的增值业务。 </p>
<p>支持交互应用 </p>
<p>我国的数字电视市场以有线数字电视为主，其独有的带宽优势为运营商开展交互业务提供了良好的基础。目前，深圳、杭州等地已经建成了全双向网络并开始为用户提供交互类的增值业务。在全双向的网络环境下运营商能够为用户提供更丰富的应用以及更个性化的服务，对用户有较强的吸引力。因此，要求中间件产品能够较好地支持主流的交互应用，如VOD(视频点播)、时移、网页浏览等，并具备一定的可扩展性以支持未来的新业务。 </p>
<p>提供本地支持能力 </p>
<p>中间件是一个非常复杂的产品，运营商在选定中间件方案之后需要与中间件产品的提供商以及机顶盒厂商进行长时间的讨论才能确定最终的技术需求。在此期间中间件厂商需要密切配合并随时根据运营商的要求对中间件产品进行优化和调整。这就要求中间件厂商能够提供强大的本地支持能力，甚至驻地开发力量，否则机顶盒的集成和应用开发的进度将难以保证。 </p>
<p>综上所述，中间件方案的使用能够解决运营商面对的种种问题，有利于降低运营成本和新业务的部署成本，对运营商有着重要的意义。 </p>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/59021.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-06-03 17:42 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/06/03/59021.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>进入加密QQ空间和盗取他人空间FLASH、图片----转载</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/06/02/58962.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Tue, 02 Jun 2009 03:22:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/06/02/58962.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/58962.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/06/02/58962.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/58962.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/58962.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 方法很多，估计都没有什么用&nbsp;&nbsp;<a href='http://www.cnitblog.com/liaoqingshan/archive/2009/06/02/58962.html'>阅读全文</a><img src ="http://www.cnitblog.com/liaoqingshan/aggbug/58962.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-06-02 11:22 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/06/02/58962.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>人气不旺</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/06/01/58944.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Mon, 01 Jun 2009 06:50:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/06/01/58944.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/58944.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/06/01/58944.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/58944.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/58944.html</trackback:ping><description><![CDATA[这论坛越来越没有人气了。<br>大家都有这感觉吧？<br>4月份发的帖子，到6月了，居然还在论坛首页。<br>人都跑QQ空间去了吧？<br>坛主也该出来忽悠一下了。
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/58944.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-06-01 14:50 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/06/01/58944.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>我想我不够爱你</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/05/26/58849.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Tue, 26 May 2009 15:11:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/05/26/58849.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/58849.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/05/26/58849.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/58849.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/58849.html</trackback:ping><description><![CDATA[我想我不够爱你，<br>我不曾忘了自己，<br>我没那么全心投入，<br>所以会一败涂地......<br>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/58849.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-05-26 23:11 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/05/26/58849.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学英语的一些好网站</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57027.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Fri, 08 May 2009 06:37:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57027.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/57027.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57027.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/57027.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/57027.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 学英语的看看，尤其是听力。&nbsp;&nbsp;<a href='http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57027.html'>阅读全文</a><img src ="http://www.cnitblog.com/liaoqingshan/aggbug/57027.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-05-08 14:37 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57027.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>7种访问对话框控件的方法</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57019.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Fri, 08 May 2009 02:48:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57019.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/57019.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57019.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/57019.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/57019.html</trackback:ping><description><![CDATA[<p>1、int num1,num2,num3;<br>char ch1[10],ch2[10],ch3[10];</p>
<p>GetDlgItem(IDC_EDIT1)-&gt;GetWindowText(ch1,10);<br>GetDlgItem(IDC_EDIT2)-&gt;GetWindowText(ch2,10);</p>
<p>num1=atoi(ch1);<br>num2=atoi(ch2);<br>num3=num1+num2;<br>itoa(num3,ch3,10);</p>
<p>GetDlgItem(IDC_EDIT3)-&gt;SetWindowText(ch3);*/</p>
<p>2、 int num1,num2,num3;<br>char ch1[10],ch2[10],ch3[10];</p>
<p>GetDlgItemText(IDC_EDIT1,ch1,10);<br>GetDlgItemText(IDC_EDIT2,ch2,10);</p>
<p>num1=atoi(ch1);<br>num2=atoi(ch2);<br>num3=num1+num2;<br>itoa(num3,ch3,10);</p>
<p>SetDlgItemText(IDC_EDIT3,ch3);*/</p>
<p>3、int num1,num2,num3;<br>num1=GetDlgItemInt(IDC_EDIT1);<br>num2=GetDlgItemInt(IDC_EDIT2);</p>
<p>num3=num1+num2;<br>SetDlgItemInt(IDC_EDIT3,num3);*/</p>
<p>4、UpdateData();<br>m_num3=m_num1+m_num2;<br>UpdateData(FALSE);*/</p>
<p>5、 int num1,num2,num3;<br>char ch1[10],ch2[10],ch3[10];</p>
<p>m_edit1.GetWindowText(ch1,10);<br>m_edit2.GetWindowText(ch2,10);</p>
<p>num1=atoi(ch1);<br>num2=atoi(ch2);<br>num3=num1+num2;<br>itoa(num3,ch3,10);</p>
<p>m_edit3.SetWindowText(ch3);*/</p>
<p>6、 int num1,num2,num3;<br>char ch1[10],ch2[10],ch3[10];</p>
<p>//::SendMessage(GetDlgItem(IDC_EDIT1)-&gt;m_hWnd,WM_GETTEXT,10,(LPARAM)ch1);<br>//::SendMessage(m_edit1.m_hWnd,WM_GETTEXT,10,(LPARAM)ch1);<br>//GetDlgItem(IDC_EDIT1)-&gt;SendMessage(WM_GETTEXT,10,(LPARAM)ch1);<br>m_edit1.SendMessage(WM_GETTEXT,10,(LPARAM)ch1);<br>m_edit2.SendMessage(WM_GETTEXT,10,(LPARAM)ch2);</p>
<p>num1=atoi(ch1);<br>num2=atoi(ch2);<br>num3=num1+num2;<br>itoa(num3,ch3,10);</p>
<p>m_edit3.SendMessage(WM_SETTEXT,0,(LPARAM)ch3);*/</p>
<p>7、int num1,num2,num3;<br>char ch1[10],ch2[10],ch3[10];</p>
<p>SendDlgItemMessage(IDC_EDIT1,WM_GETTEXT,10,(LPARAM)ch1);<br>SendDlgItemMessage(IDC_EDIT2,WM_GETTEXT,10,(LPARAM)ch2);</p>
<p>num1=atoi(ch1);<br>num2=atoi(ch2);<br>num3=num1+num2;<br>itoa(num3,ch3,10);</p>
<p>SendDlgItemMessage(IDC_EDIT3,WM_SETTEXT,0,(LPARAM)ch3);<br>SendDlgItemMessage(IDC_EDIT3,EM_SETSEL,0,-1);<br>m_edit3.SetFocus();<br></p>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/57019.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-05-08 10:48 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/05/08/57019.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>五二-动物园</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56922.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Tue, 05 May 2009 02:37:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56922.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56922.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56922.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56922.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56922.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 看来我还是愿意跟开朗的人在一起。&nbsp;&nbsp;<a href='http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56922.html'>阅读全文</a><img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56922.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-05-05 10:37 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56922.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>2008-09赛季NBA季后赛第二轮赛程表（火箭-湖人）</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56918.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Tue, 05 May 2009 01:08:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56918.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56918.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56918.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56918.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56918.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 今天10：30直播第一场&nbsp;&nbsp;<a href='http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56918.html'>阅读全文</a><img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56918.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-05-05 09:08 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/05/05/56918.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>windows编程中的字符集问题小结----转载</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/30/56839.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Thu, 30 Apr 2009 07:39:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/30/56839.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56839.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/30/56839.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56839.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56839.html</trackback:ping><description><![CDATA[<p>1.一共有三种字符集：单字节字符集single-byte charecter set(如ascii和扩展ascii)，不定字节字符集multi-byte charecter set（如双字节字符集：Double-byte charecter set），wide-character set宽字符集（如Unicode）；</p>
<p>2.windows 98由于是继承自16位操作系统的32位操作系统，而16位操作系统只使用ANSI编码，故windows98只支持ANSI编码,windows系统调用函数的参数都必须是ANSI编码；<br>&nbsp;&nbsp;&nbsp;&nbsp; windows 2000是一个不同于windows 98的全新操作（即它是重新开发的），它的系统调用函数的参数只支持Unicode，但是它同时也通过一种间接转换来支持ANSI参数的系统调用，即当参数是ANSI时，操作系统会将ANSI转化为Unicode，然后将转换后的参数传给被调用的这个系统函数，同理，若这个系统函数返回值是ANSI,操作系统也会将Unicode转换为ANSI，然后传递这个返回值；因此说windows 2000是既支持ANSI也支持Unicode，但是使用ANSI会降低系统的性能；<br>&nbsp;&nbsp;&nbsp;&nbsp; windows ce只支持Unicode；</p>
<p>3.C语言运行库（由微软提供的）也支持这3种字符集<br>ASCII的数据类型为char(winnt.h中有typedef char CHAR);<br>Unicode(既宽字符）的数据类型为wchar_t(在wchar.h，string.h中有typedef unsigned short wchar_t);<br>&nbsp;&nbsp; string.h中同时支持ANSI和Unicode，当参数为ANSI的函数前缀是str，参数为Unicode的函数的前缀是wcs;微软提供的C语言运行库中默认的字符串是ANSI,即字符串"我们"采用的是ANSI,L"我们"才是Unicode；</p>
<p>3.可以利用TCHAR.h和宏_UNICODE来无区别的调用ANSI和UNICODE参数的函数，_T这个宏将根据是否定义了_UNICODE宏来自动在俩者之间转换，<br>#define __T(x)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; L ## x<br>#define __T(x)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x</p>
<p>4.windows程序的库：windows.h,winnt.h,<br>&nbsp;&nbsp; Microsoft提供的C语言运行库：TCHAR.h,string.h<br>&nbsp;&nbsp; 在windows程序的库中，Unicode的宏是UNICODE<br>&nbsp;&nbsp; 在Microsoft提供的C语言运行库中，Unicode的宏是_UNICODE</p>
<p>5.在c中，char代表字节数据类型；在java中，char代表字符数据类型（内存可能占一个或倆个字节），byte代表字节数据类型;</p>
<p>6.C中，字符串char *代表的是ansi编码，ansi不是一种确定的编码，那些所有使用 2 个字节来代表一个字符的各种汉字延伸编码方式，称为 ANSI 编码。在简体中文系统下，ANSI 编码代表 GB2312 编码，在繁体中文系统下，ANSI 编码代表 BIG5 编码，在日文操作系统下，ANSI 编码代表 JIS 编码;</p>
<p>7.UNICODE 包含了各种语言里面所使用到的所有字符，并为每一个字符赋予了一个唯一的序号。不论在什么平台下，什么程序中，不论用在什么语言里，UNICODE 对同一个字符赋予的序号总是相同的。与其他&#8220;字符集&#8221;不同的是，&#8220;UNICODE&#8221;并不代表一种&#8220;编码&#8221;，只代表一个&#8220;字符的集合&#8221;。用来给 UNICODE 字符集编码的标准有很多种，比如：UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等</p>
<p>8.在 UNICODE 被采用之前，计算机想要记录一段文字，内存中实际存放的内容是：按照指定编码规则得到的字节串。也就是按照 ANSI 编码方式存储在内存中的。比如：在中文 DOS, Windows 95, Windows 98 操作系统中，字符串 "中文123" 存放在内存中时，实际存放的是 [D6][D0][CE][C4][31][32][33] 这7个字节。（'中' 和 '文' 分别占2个字节。）<br>&nbsp;&nbsp;&nbsp; 而在 UNICODE 被采用之后，计算机想要记录一段文字时，内存中不再存放根据特定编码而得到的字节串，而改为存放各个字符在 UNICODE 中的序号。比如：在 Windows NT/2000/XP, Linux, Java 系统中，字符串 "中文123" 存放在内存中时，实际记录的是 20013, 25991, 49, 50, 51 这5个序号。当不同语言中的字符需要同时表示时，不会因为编码冲突而无法表示。<br>&nbsp;&nbsp;&nbsp; 字符串实际所占内存空间的大小，要取决于当前系统采用多少字节来存放一个&#8220;序号&#8221;。如果使用2个字节存放一个序号，那么 "中文123" 在内存中就占10个字节。如果使用4个字节存放一个序号，那么 "中文123" 就占20个字节。</p>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56839.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-30 15:39 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/30/56839.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>宽字符集----转载</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/30/56838.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Thu, 30 Apr 2009 07:37:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/30/56838.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56838.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/30/56838.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56838.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56838.html</trackback:ping><description><![CDATA[宽字元和C <br><br>对C程式写作者来说，16位元字元的想法的确让人扫兴。一个char和一个位元组同宽是最不能确定的事情之一。没几个程式写作者清楚ANSI/ISO 9899-1990，这是「美国国家标准程式设计语言－C」（也称作「ANSI C」）通过一个称作「宽字元」的概念来支援用多个位元组代表一字元的字元集。这些宽字元与常用的字元完美地共存。 <br><br>ANSI C也支援多位元组字元集，例如中文、日文和韩文版本Windows支援的字元集。然而，这些多位元组字元集被当成单位元组构成的字串看待，只不过其中一些字元改变了后续字元的含义而已。多位元组字元集主要影响C语言程式执行时期程式库函式。相比之下，宽字元比正常字元宽，而且会引起一些编译问题。 <br><br>宽字元不需要是Unicode。Unicode是一种可能的宽字元集。然而，因为本书的焦点是Windows而不是C执行的理论，所以我将把宽字元和Unicode作为同义语。 <br><br>char资料型态 <br><br><br>假定我们都非常熟悉在C程式中使用char资料型态来定义和储存字元跟字串。但为了便於理解C如何处理宽字元，让我们先回顾一下可能在Win32程式中出现的标准字元定义。 <br><br>下面的语句定义并初始化了一个只包含一个字元的变数： <br><br>char c = 'A' ; <br>变数c需要1个位元组来保存，并将用十六进位数0x41初始化，这是字母A的ASCII代码。 <br><br>您可以像这样定义一个指向字串的指标： <br><br>char * p ; <br>因为Windows是一个32位元作业系统，所以指标变数p需要用4个位元组保存。您还可初始化一个指向字串的指标： <br><br>char * p = "Hello!" ; <br>像前面一样，变数p也需要用4个位元组保存。该字串保存在静态记忆体中并占用7个位元组－6个位元组保存字串，另1个位元组保存终止符号0。 <br><br>您还可以像这样定义字元阵列： <br><br>char a[10] ; <br>在这种情况下，编译器为该阵列保留了10个位元组的储存空间。运算式sizeof（a） 将返回10。如果阵列是整体变数（即在所有函式外定义），您可使用像下面的语句来初始化一个字元阵列： <br><br>char a[] = "Hello!" ; <br>如果您将该阵列定义为一个函式的区域变数，则必须将它定义为一个static变数，如下： <br><br>static char a[] = "Hello!" ; <br>无论哪种情况，字串都储存在静态程式记忆体中，并在末尾添加0，这样就需要7个位元组的储存空间。 <br><br>宽字元 <br><br><br>Unicode或者宽字元都没有改变char资料型态在C中的含义。char继续表示1个位元组的储存空间， sizeof （char） 继续返回1。理论上，C中1个位元组可比8位元长，但对我们大多数人来说，1个位元组（也就是1个char）是8位元宽。 <br><br>C中的宽字元基於wchar_t资料型态，它在几个表头档案包括WCHAR.H中都有定义，像这样： <br><br>typedef unsigned short wchar_t ; <br>因此，wchar_t资料型态与无符号短整数型态相同，都是16位元宽。 <br><br>要定义包含一个宽字元的变数，可使用下面的语句： <br><br>wchar_t c = 'A' ; <br>变数c是一个双位元组值0x0041，是Unicode表示的字母A。（然而，因为Intel微处理器从最小的位元组开始储存多位元组数值，该位元组实际上是以0x41、0x00的顺序保存在记忆体中。如果检查Unicode文字的电脑储存应注意这一点。） <br><br>您还可定义指向宽字串的指标： <br><br>wchar_t * p = L"Hello!" ; <br>注意紧接在第一个引号前面的大写字母L（代表「long」）。这将告诉编译器该字串按宽字元保存－即每个字元占用2个位元组。通常，指标变数p要占用4个位元组，而字串变数需要14个位元组－每个字元需要2个位元组，末尾的0还需要2个位元组。 <br><br>同样，您还可以用下面的语句定义宽字元阵列： <br><br>static wchar_t a[] = L"Hello!" ; <br>该字串也需要14个位元组的储存空间，sizeof (a) 将返回14。索引阵列a可得到单独的字元。a[1] 的值是宽字元「e」，或者0x0065。 <br><br>虽然看上去更像一个印刷符号，但第一个引号前面的L非常重要，并且在两个符号之间必须没有空格。只有带有L，编译器才知道您需要将字串存为每个字元2位元组。稍后，当我们看到使用宽字串而不是变数定义时，您还会遇到第一个引号前面的L。幸运的是，如果忘记了包含L，C编译器通常会给提出警告或错误资讯。 <br><br>您还可在单个字元文字前面使用L字首，来表示它们应解释为宽字元。如下所示： <br><br>wchar_t c = L'A' ; <br>但通常这是不必要的，C编译器会对该字元进行扩充，使它成为宽字元。 <br><br>宽字元程式库函式 <br><br><br>我们都知道如何获得字串的长度。例如，如果我们已经像下面这样定义了一个字串指标： <br><br>char * pc = "Hello!" ; <br>我们可以呼叫 <br><br>iLength = strlen (pc) ; <br>这时变数iLength将等於6，也就是字串中的字元数。 <br><br>太好了！现在让我们试著定义一个指向宽字元的指标： <br><br>wchar_t * pw = L"Hello!" ; <br>再次呼叫strlen ： <br><br>iLength = strlen (pw) ; <br>现在麻烦来了。首先，C编译器会显示一条警告消息，可能是这样的内容： <br><br>'function' : incompatible types - from 'unsigned short *' to 'const char *' <br><br>这条消息的意思是：宣告strlen函式时，该函式应接收char类型的指标，但它现在却接收了一个unsigned short类型的指标。您仍然可编译并执行该程式，但您会发现iLength等於1。为什么？ <br><br>字串「Hello!」中的6个字元占用16位元： <br><br>0x0048 0x0065 0x006C 0x006C 0x006F 0x0021 <br>Intel处理器在记忆体中将其存为： <br><br>48 00 65 00 6C 00 6C 00 6F 00 21 00 <br>假定strlen函式正试图得到一个字串的长度，并把第1个位元组作为字元开始计数，但接著假定如果下一个位元组是0，则表示字串结束。 <br><br>这个小练习清楚地说明了C语言本身和执行时期程式库函式之间的区别。编译器将字串L"Hello!" 解释为一组16位元短整数型态资料，并将其保存在wchar_t阵列中。编译器还处理阵列索引和sizeof操作符，因此这些都能正常工作，但在连结时才添加执行时期程式库函式，例如strlen。这些函式认为字串由单位元组字元组成。遇到宽字串时，函式就不像我们所希望那样执行了。 <br><br>您可能要说：「噢，太麻烦了！」现在每个C语言程式库函式都必须重写以接受宽字元。但事实上并不是每个C语言程式库函式都需要重写，只是那些有字串参数的函式才需要重写，而且也不用由您来完成。它们已经重写完了。 <br><br>strlen函式的宽字元版是wcslen（wide-character string length：宽字串长度），并且在STRING.H（其中也说明了strlen）和WCHAR.H中均有说明。strlen函式说明如下： <br><br>size_t __cdecl strlen (const char *) ; <br>而wcslen函式则说明如下： <br><br>size_t __cdecl wcslen (const wchar_t *) ; <br>这时我们知道，要得到宽字串的长度可以呼叫 <br><br>iLength = wcslen (pw) ; <br>函式将返回字串中的字元数6。请记住，改成宽位元组后，字串的字元长度不改变，只是位元组长度改变了。 <br><br>您熟悉的所有带有字串参数的C执行时期程式库函式都有宽字元版。例如，wprintf是printf的宽字元版。这些函式在WCHAR.H和含有标准函式说明的表头档案中说明。 <br><br>维护单一原始码 <br><br><br>当然，使用Unicode也有缺点。第一点也是最主要的一点是，程式中的每个字串都将占用两倍的储存空间。此外，您将发现宽字元执行时期程式库中的函式比常规的函式大。出於这个原因，您也许想建立两个版本的程式－一个处理ASCII字串，另一个处理Unicode字串。最好的解决办法是维护既能按ASCII编译又能按Unicode编译的单一原始码档案。 <br><br>虽然只是一小段程式，但由於执行时期程式库函式有不同的名称，您也要定义不同的字元，这将在处理前面有L的字串文字时遇到麻烦。 <br><br>一个办法是使用Microsoft Visual C++包含的TCHAR.H表头档案。该表头档案不是ANSI C标准的一部分，因此那里定义的每个函式和巨集定义的前面都有一条底线。TCHAR.H为需要字串参数的标准执行时期程式库函式提供了一系列的替代名称（例如，_tprintf和_tcslen）。有时这些名称也称为「通用」函式名称，因为它们既可以指向函式的Unicode版也可以指向非Unicode版。 <br><br>如果定义了名为_UNICODE的识别字，并且程式中包含了TCHAR.H表头档案，那么_tcslen就定义为wcslen： <br><br>#define _tcslen wcslen <br>如果没有定义UNICODE，则_tcslen定义为strlen： <br><br>#define _tcslen strlen <br>等等。TCHAR.H还用一个新的资料型态TCHAR来解决两种字元资料型态的问题。如果定义了 _UNICODE识别字，那么TCHAR就是wchar_t： <br><br>typedef wchar_t TCHAR ; <br>否则，TCHAR就是char： <br><br>typedef char TCHAR ; <br>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56838.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-30 15:37 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/30/56838.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Installing MySQL on Vista RC1----转载</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56761.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Mon, 27 Apr 2009 07:07:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56761.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56761.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56761.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56761.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56761.html</trackback:ping><description><![CDATA[回去试一下，看是不是真的有用.<br>well there is a bug on .msi installation file where you can't run the service after install. however, i can run MySQL on Vista RC1 just fine using the .zip file. <br><br>1. get the mysql .zip then exact to c:\mysql then coppy my-XXXX.ini to readable path <br>2. on your Vista RC1, go to c:\mysql\bin <br>3. right-click mysqld.exe and click Properties <br>4. in the compatibility tab, check the Run this program as an Administrator -&gt; ok <br>5. go to cmd.exe and run mysql c:\&gt; "c:\mysql\bin\mysqld" --console <br>if you see one line that said "ready to connect" mean you can open another cmd.exe and connect: mysql -u root mysql) <br>6. add MySQL to service list: c:\&gt; "c:\mysql\bin\mysqld" --install (so that next time you don't have to run MySQL again)<br>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56761.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-27 15:07 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56761.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>与异性同居的日子</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56755.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Mon, 27 Apr 2009 06:23:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56755.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56755.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56755.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56755.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56755.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 好开朗的一个女孩。&nbsp;&nbsp;<a href='http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56755.html'>阅读全文</a><img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56755.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-27 14:23 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/27/56755.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>笑语95则未满----只留下些经典的</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/24/56693.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Fri, 24 Apr 2009 06:39:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/24/56693.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56693.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/24/56693.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56693.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56693.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 你快回来，我一人忽悠不来！&nbsp;&nbsp;<a href='http://www.cnitblog.com/liaoqingshan/archive/2009/04/24/56693.html'>阅读全文</a><img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56693.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-24 14:39 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/24/56693.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学历太重要了！(经典啊) </title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56635.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Thu, 23 Apr 2009 03:21:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56635.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56635.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56635.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56635.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56635.html</trackback:ping><description><![CDATA[<p>&nbsp;首先，网上出现了这样一个帖子： </p>
<p>　　我是博士生,我深知学习的重要性.通过这么多年的学习,我已成功地使自已由一个农家子变成了处级干部,现在不但有专车接我上下班,而且还住着一百多平方的房子月收入达到了三千多块,还娶了一个漂亮的太太,我过上了幸福的生活.我感谢生活&#8230; </p>
<p>&nbsp; 然后，各式各样的回帖就出现啦 </p>
<p>　　　a回贴如下: <br>　　　我是硕士生,我深知知识的重要,于是我选择了学医.现在是某眼科医院的主治医生.月入万元有余,我不但买了房,还轻松地供了车.医院最漂亮的护士mm,天天要求我和她结婚.说实话,这婚有什么好结的.在一起睡不就得了 </p>
<p>　　　b回贴如下: <br>　　　我是本科生.现在一房地产公司搞策划.去年分红才十几万.真是一年不如一年,前年还分到二十多万,今年就成了这样&#8230;.. </p>
<p>　　　c回贴如下: <br>　　　我是大专生,经过多年的努力,终于当上了公司的财务经理.也不知为什么,老总对我总是那么好.不但月月给我六位数的工资.而且还送了一套房子和一辆汔车给我.银行里的钱够我好好过完这辈子了&#8230;..对了,我们是上市公司. </p>
<p>　　　d回贴如下: <br>　　　我是中专生.唉,也就算个高中文凭.找不到好的工作于是就做了报关员.我充分启动了我的大脑.也有房有车了.与你不同的是,这些都是我自已买的.想想挺不容易.我儿子上美国留学的钱,我都替他存够了.下一步计划,就是找谁替我生个儿子&#8230; </p>
<p>　　　e回贴如下: <br>　　　你们吵什么吵呀?我没什么文化.初中都没毕业.找不到好工作,只好天天在家打麻将.由于没有文化吧.算牌老是不准.今年输了一百多万.对了,你们要是有兴趣,有空一起打牌呀. 我家住南方某某村的.我爸是村长.我的电话是139029x88888&#8230; </p>
<p>　　　f回贴如下： <br>　　　我是文盲一不小心当上了董事长,手下只有二十六个上市公司,几个不孝子一天口就要300万去唱ktv,改天再找一个14娘好好管管这几个不孝子 </p>
<p>　　　g回贴如下： <br>　　　我是法盲，一不小心当上黑社会老大，手下兄弟百十个，掌管60多条街，若干店铺和娱乐城，每年收入几个亿，每天傍我的妞几十个。对了，由于没有文化，现在正请博士帮忙上市呢 </p>
<p>　　　h回贴如下： <br>　　　我是白痴，他们选我当美国总统，我没事就打打阿富汗，攻攻伊拉克，死它一两万.一天花他几个亿$ </p>
<p>　　　i回贴如下： <br>　　　我是一个坐台小姐,几个月前被一个有钱的老板看上把我包了起来,特地给我买了一栋豪华别墅,在我的户头里存进了500万,就那么点钱真不知道他是怎么想的,前几天买了一支口红花了10万,在洗手间补妆的时候不小心掉马桶里了,掉了就掉了吧,才那么几个钱,不过上个星期去拉丝维加丝,赌了一把,手气真背,一下子就输了1000万,害得我只能坐我的专用直升飞机回家,本来想坐那些平民喜欢坐的民航感受一回的,咳,下次吧,现在我的户头上有2000万,又能玩几把了 </p>
<p>　　　j回帖如下: <br>　　　我是一个乞丐本来在伊拉克行乞，这些天因为打仗所以转移了地方，本想在国外的再做本行的，因为进行资格审查时查到我有3亿美元（还好只找到我的九牛一毛，不要交税就交死了）的资产不发给我行乞执照，听说国内还没有实行执照上岗所以又回来了，搬家真不容易，带着我一家大小128口真不方便，因为怕有人劫机所以自己买了一个波音飞机飞了回来，没有地方放，就1折卖给了民航。大家也知道我们这行的，白天干活晚上还是要休息的嘛，所以买了43公顷的一块小地随便造了几栋30至70层不等小楼，为了工作方便买了几辆小车，人太多嘛所以加长了一点，白天开到北京好工作，呀不能再说了，不要会有人抢饭碗了 </p>
<p>　　　k回贴如下： <br>　　　俺是一个渔民，从小就在海边长大，没上过学。那些什么汉字、英文、法文、德文、俄文、西班牙文、拉丁文&#8230;&#8230;都是俺收集各个国家小人书，边看边自学的。现在联合国有点啥交流方面搞不定的场合还得请我去，我不爱去，一小时才10000$，没意思，还不如在家打鱼挣的多呢！俺从小就喜欢动手做点小东西啥的，天生手比较巧，什么遥控模型飞机了，俺2岁就会做了。现在咱家后院里一个叫太平洋的小水坑里，停的10艘航母也是俺自己一点点做出来的，这批已经被伊拉克买下来了。还有一批订单，是北朝鲜定的10艘，准备今夏交货。每天忙的不行，钱啥的对我来说只是数据，都放在美国花旗银行里了，我要是哪天不高兴，把钱提出来。花旗立刻破产&#8230;&#8230;</p>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56635.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-23 11:21 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56635.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>华为软件编程规范和范例----转载</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56634.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Thu, 23 Apr 2009 03:18:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56634.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56634.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56634.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56634.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56634.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: [ 华为软件编程规范和范例 ] 转自： http://www.cppblog.com/oosky/archive/2006/03/26/4625.html#_Toc131314730 &nbsp; 〔一〕=====[排版] ]=======. 〔二〕======[注释]=======. 〔三〕=====[标识符命名]=======. 〔四〕=====[可读性]======. ...&nbsp;&nbsp;<a href='http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56634.html'>阅读全文</a><img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56634.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-23 11:18 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56634.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>堆和栈的区别 (转贴) </title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56631.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Thu, 23 Apr 2009 03:12:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56631.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56631.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56631.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56631.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56631.html</trackback:ping><description><![CDATA[<div class=postbody>
<h2>堆和栈的区别&nbsp;(转贴) </h2>
<p>非本人作也!因非常经典,所以收归旗下,与众人阅之!原作者不祥!</p>
<div class=postbody>堆和栈的区别<br>一、预备知识—程序的内存分配<br>一个由c/C++编译的程序占用的内存分为以下几个部分<br>1、栈区（stack）—&nbsp;由编译器自动分配释放&nbsp;，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。<br>2、堆区（heap）&nbsp;—&nbsp;一般由程序员分配释放，&nbsp;若程序员不释放，程序结束时可能由OS回收&nbsp;。注意它与数据结构中的堆是两回事，分配方式倒是类似于链表，呵呵。<br>3、全局区（静态区）（static）—，全局变量和静态变量的存储是放在一块的，初始化的全局变量和静态变量在一块区域，&nbsp;未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。&nbsp;-&nbsp;程序结束后有系统释放&nbsp;<br>4、文字常量区—常量字符串就是放在这里的。&nbsp;程序结束后由系统释放<br>5、程序代码区—存放函数体的二进制代码。<br>二、例子程序&nbsp;<br>这是一个前辈写的，非常详细&nbsp;<br>//main.cpp&nbsp;<br>int&nbsp;a&nbsp;=&nbsp;0;&nbsp;全局初始化区&nbsp;<br>char&nbsp;*p1;&nbsp;全局未初始化区&nbsp;<br>main()&nbsp;<br>{&nbsp;<br>int&nbsp;b;&nbsp;栈&nbsp;<br>char&nbsp;s[]&nbsp;=&nbsp;"abc";&nbsp;栈&nbsp;<br>char&nbsp;*p2;&nbsp;栈&nbsp;<br>char&nbsp;*p3&nbsp;=&nbsp;"123456";&nbsp;123456\0在常量区，p3在栈上。&nbsp;<br>static&nbsp;int&nbsp;c&nbsp;=0；&nbsp;全局（静态）初始化区&nbsp;<br>p1&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>p2&nbsp;=&nbsp;(char&nbsp;*)malloc(20);&nbsp;<br>分配得来得10和20字节的区域就在堆区。&nbsp;<br>strcpy(p1,&nbsp;"123456");&nbsp;123456\0放在常量区，编译器可能会将它与p3所指向的"123456"优化成一个地方。&nbsp;<br>}&nbsp;
<p>&#160;</p>
<p><br>二、堆和栈的理论知识&nbsp;<br>2.1申请方式&nbsp;<br>stack:&nbsp;<br>由系统自动分配。&nbsp;例如，声明在函数中一个局部变量&nbsp;int&nbsp;b;&nbsp;系统自动在栈中为b开辟空间&nbsp;<br>heap:&nbsp;<br>需要程序员自己申请，并指明大小，在c中malloc函数&nbsp;<br>如p1&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>在C++中用new运算符&nbsp;<br>如p2&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>但是注意p1、p2本身是在栈中的。&nbsp;</p>
<p><br>2.2&nbsp;<br>申请后系统的响应&nbsp;<br>栈：只要栈的剩余空间大于所申请空间，系统将为程序提供内存，否则将报异常提示栈溢出。&nbsp;<br>堆：首先应该知道操作系统有一个记录空闲内存地址的链表，当系统收到程序的申请时，&nbsp;<br>会遍历该链表，寻找第一个空间大于所申请空间的堆结点，然后将该结点从空闲结点链表中删除，并将该结点的空间分配给程序，另外，对于大多数系统，会在这块内存空间中的首地址处记录本次分配的大小，这样，代码中的delete语句才能正确的释放本内存空间。另外，由于找到的堆结点的大小不一定正好等于申请的大小，系统会自动的将多余的那部分重新放入空闲链表中。&nbsp;</p>
<p>2.3申请大小的限制&nbsp;<br>栈：在Windows下,栈是向低地址扩展的数据结构，是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的，在WINDOWS下，栈的大小是2M（也有的说是1M，总之是一个编译时就确定的常数），如果申请的空间超过栈的剩余空间时，将提示overflow。因此，能从栈获得的空间较小。&nbsp;<br>堆：堆是向高地址扩展的数据结构，是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的，自然是不连续的，而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见，堆获得的空间比较灵活，也比较大。&nbsp;</p>
<p><br>2.4申请效率的比较：&nbsp;<br>栈由系统自动分配，速度较快。但程序员是无法控制的。&nbsp;<br>堆是由new分配的内存，一般速度比较慢，而且容易产生内存碎片,不过用起来最方便.&nbsp;<br>另外，在WINDOWS下，最好的方式是用VirtualAlloc分配内存，他不是在堆，也不是在栈是直接在进程的地址空间中保留一快内存，虽然用起来最不方便。但是速度快，也最灵活。&nbsp;</p>
<p>2.5堆和栈中的存储内容&nbsp;<br>栈：&nbsp;在函数调用时，第一个进栈的是主函数中后的下一条指令（函数调用语句的下一条可执行语句）的地址，然后是函数的各个参数，在大多数的C编译器中，参数是由右往左入栈的，然后是函数中的局部变量。注意静态变量是不入栈的。&nbsp;<br>当本次函数调用结束后，局部变量先出栈，然后是参数，最后栈顶指针指向最开始存的地址，也就是主函数中的下一条指令，程序由该点继续运行。&nbsp;<br>堆：一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。&nbsp;</p>
<p>2.6存取效率的比较&nbsp;</p>
<p>char&nbsp;s1[]&nbsp;=&nbsp;"aaaaaaaaaaaaaaa";&nbsp;<br>char&nbsp;*s2&nbsp;=&nbsp;"bbbbbbbbbbbbbbbbb";&nbsp;<br>aaaaaaaaaaa是在运行时刻赋值的；&nbsp;<br>而bbbbbbbbbbb是在编译时就确定的；&nbsp;<br>但是，在以后的存取中，在栈上的数组比指针所指向的字符串(例如堆)快。&nbsp;<br>比如：&nbsp;<br>#include&nbsp;<br>void&nbsp;main()&nbsp;<br>{&nbsp;<br>char&nbsp;a&nbsp;=&nbsp;1;&nbsp;<br>char&nbsp;c[]&nbsp;=&nbsp;"1234567890";&nbsp;<br>char&nbsp;*p&nbsp;="1234567890";&nbsp;<br>a&nbsp;=&nbsp;c[1];&nbsp;<br>a&nbsp;=&nbsp;p[1];&nbsp;<br>return;&nbsp;<br>}&nbsp;<br>对应的汇编代码&nbsp;<br>10:&nbsp;a&nbsp;=&nbsp;c[1];&nbsp;<br>00401067&nbsp;8A&nbsp;4D&nbsp;F1&nbsp;mov&nbsp;cl,byte&nbsp;ptr&nbsp;[ebp-0Fh]&nbsp;<br>0040106A&nbsp;88&nbsp;4D&nbsp;FC&nbsp;mov&nbsp;byte&nbsp;ptr&nbsp;[ebp-4],cl&nbsp;<br>11:&nbsp;a&nbsp;=&nbsp;p[1];&nbsp;<br>0040106D&nbsp;8B&nbsp;55&nbsp;EC&nbsp;mov&nbsp;edx,dword&nbsp;ptr&nbsp;[ebp-14h]&nbsp;<br>00401070&nbsp;8A&nbsp;42&nbsp;01&nbsp;mov&nbsp;al,byte&nbsp;ptr&nbsp;[edx+1]&nbsp;<br>00401073&nbsp;88&nbsp;45&nbsp;FC&nbsp;mov&nbsp;byte&nbsp;ptr&nbsp;[ebp-4],al&nbsp;<br>第一种在读取时直接就把字符串中的元素读到寄存器cl中，而第二种则要先把指针值读到edx中，在根据edx读取字符，显然慢了。&nbsp;</p>
<p><br>2.7小结：&nbsp;<br>堆和栈的区别可以用如下的比喻来看出：&nbsp;<br>使用栈就象我们去饭馆里吃饭，只管点菜（发出申请）、付钱、和吃（使用），吃饱了就走，不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作，他的好处是快捷，但是自由度小。&nbsp;<br>使用堆就象是自己动手做喜欢吃的菜肴，比较麻烦，但是比较符合自己的口味，而且自由度大。&nbsp;<br><br><br><br></p>
<p>windows进程中的内存结构</p>
<p><br>在阅读本文之前，如果你连堆栈是什么多不知道的话，请先阅读文章后面的基础知识。&nbsp;</p>
<p>接触过编程的人都知道，高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢？程序又是如何使用这些变量的呢？下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明，默认都使用VC编译的release版。&nbsp;</p>
<p>首先，来了解一下&nbsp;C&nbsp;语言的变量是如何在内存分部的。C&nbsp;语言有全局变量(Global)、本地变量(Local)，静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>int&nbsp;g1=0,&nbsp;g2=0,&nbsp;g3=0;&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>static&nbsp;int&nbsp;s1=0,&nbsp;s2=0,&nbsp;s3=0;&nbsp;<br>int&nbsp;v1=0,&nbsp;v2=0,&nbsp;v3=0;&nbsp;</p>
<p>//打印出各个变量的内存地址&nbsp;</p>
<p>printf("0x%08x\n",&amp;v1);&nbsp;//打印各本地变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;v2);&nbsp;<br>printf("0x%08x\n\n",&amp;v3);&nbsp;<br>printf("0x%08x\n",&amp;g1);&nbsp;//打印各全局变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;g2);&nbsp;<br>printf("0x%08x\n\n",&amp;g3);&nbsp;<br>printf("0x%08x\n",&amp;s1);&nbsp;//打印各静态变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;s2);&nbsp;<br>printf("0x%08x\n\n",&amp;s3);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后的执行结果是：&nbsp;</p>
<p>0x0012ff78&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff80&nbsp;</p>
<p>0x004068d0&nbsp;<br>0x004068d4&nbsp;<br>0x004068d8&nbsp;</p>
<p>0x004068dc&nbsp;<br>0x004068e0&nbsp;<br>0x004068e4&nbsp;</p>
<p>输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量，g1,g2,g3是全局变量，s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的，但是本地变量和全局变量分配的内存地址差了十万八千里，而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言，可以在逻辑上分成3个部份：代码区，静态数据区和动态数据区。动态数据区一般就是&#8220;堆栈&#8221;。&#8220;栈(stack)&#8221;和&#8220;堆(heap)&#8221;是两种不同的动态数据区，栈是一种线性结构，堆是一种链式结构。进程的每个线程都有私有的&#8220;栈&#8221;，所以每个线程虽然代码一样，但本地变量的数据都是互不干扰。一个堆栈可以通过&#8220;基地址&#8221;和&#8220;栈顶&#8221;地址来描述。全局变量和静态变量分配在静态数据区，本地变量分配在动态数据区，即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。&nbsp;</p>
<p><br>├———————┤低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;动态数据区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;代码区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;静态数据区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤高端内存区域&nbsp;</p>
<p><br>堆栈是一个先进后出的数据结构，栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程，以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定，这些因素有参数的压入规则和堆栈的平衡。windows&nbsp;API的调用规则和ANSI&nbsp;C的函数调用规则是不一样的，前者由被调函数调整堆栈，后者由调用者调整堆栈。两者通过&#8220;__stdcall&#8221;和&#8220;__cdecl&#8221;前缀区分。先看下面这段代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>void&nbsp;__stdcall&nbsp;func(int&nbsp;param1,int&nbsp;param2,int&nbsp;param3)&nbsp;<br>{&nbsp;<br>int&nbsp;var1=param1;&nbsp;<br>int&nbsp;var2=param2;&nbsp;<br>int&nbsp;var3=param3;&nbsp;<br>printf("0x%08x\n",&#182;m1);&nbsp;//打印出各个变量的内存地址&nbsp;<br>printf("0x%08x\n",&#182;m2);&nbsp;<br>printf("0x%08x\n\n",&#182;m3);&nbsp;<br>printf("0x%08x\n",&amp;var1);&nbsp;<br>printf("0x%08x\n",&amp;var2);&nbsp;<br>printf("0x%08x\n\n",&amp;var3);&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>func(1,2,3);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后的执行结果是：&nbsp;</p>
<p>0x0012ff78&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff80&nbsp;</p>
<p>0x0012ff68&nbsp;<br>0x0012ff6c&nbsp;<br>0x0012ff70&nbsp;</p>
<p><br>├———————┤&lt;—函数执行时的栈顶（ESP）、低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;1&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;2&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;3&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;RET&nbsp;│&nbsp;<br>├———————┤&lt;—&#8220;__cdecl&#8221;函数返回后的栈顶（ESP）&nbsp;<br>│&nbsp;parameter&nbsp;1&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;parameter&nbsp;2&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;parameter&nbsp;3&nbsp;│&nbsp;<br>├———————┤&lt;—&#8220;__stdcall&#8221;函数返回后的栈顶（ESP）&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—栈底（基地址&nbsp;EBP）、高端内存区域&nbsp;</p>
<p><br>上图就是函数调用过程中堆栈的样子了。首先，三个参数以从又到左的次序压入堆栈，先压&#8220;param3&#8221;，再压&#8220;param2&#8221;，最后压入&#8220;param1&#8221;；然后压入函数的返回地址(RET)，接着跳转到函数地址接着执行（这里要补充一点，介绍UNIX下的缓冲溢出原理的文章中都提到在压入RET后，继续压入当前EBP，然后用当前ESP代替EBP。然而，有一篇介绍windows下函数调用的文章中说，在windows下的函数调用也有这一步骤，但根据我的实际调试，并未发现这一步，这还可以从param3和var1之间只有4字节的间隙这点看出来）；第三步，将栈顶(ESP)减去一个数，为本地变量分配内存空间，上例中是减去12字节(ESP=ESP-3*4，每个int变量占用4个字节)；接着就初始化本地变量的内存空间。由于&#8220;__stdcall&#8221;调用由被调函数调整堆栈，所以在函数返回前要恢复堆栈，先回收本地变量占用的内存(ESP=ESP+3*4)，然后取出返回地址，填入EIP寄存器，回收先前压入参数占用的内存(ESP=ESP+3*4)，继续执行调用者的代码。参见下列汇编代码：&nbsp;</p>
<p>;--------------func&nbsp;函数的汇编代码-------------------&nbsp;</p>
<p>:00401000&nbsp;83EC0C&nbsp;sub&nbsp;esp,&nbsp;0000000C&nbsp;//创建本地变量的内存空间&nbsp;<br>:00401003&nbsp;8B442410&nbsp;mov&nbsp;eax,&nbsp;dword&nbsp;ptr&nbsp;[esp+10]&nbsp;<br>:00401007&nbsp;8B4C2414&nbsp;mov&nbsp;ecx,&nbsp;dword&nbsp;ptr&nbsp;[esp+14]&nbsp;<br>:0040100B&nbsp;8B542418&nbsp;mov&nbsp;edx,&nbsp;dword&nbsp;ptr&nbsp;[esp+18]&nbsp;<br>:0040100F&nbsp;89442400&nbsp;mov&nbsp;dword&nbsp;ptr&nbsp;[esp],&nbsp;eax&nbsp;<br>:00401013&nbsp;8D442410&nbsp;lea&nbsp;eax,&nbsp;dword&nbsp;ptr&nbsp;[esp+10]&nbsp;<br>:00401017&nbsp;894C2404&nbsp;mov&nbsp;dword&nbsp;ptr&nbsp;[esp+04],&nbsp;ecx&nbsp;</p>
<p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;（省略若干代码）&nbsp;</p>
<p>:00401075&nbsp;83C43C&nbsp;add&nbsp;esp,&nbsp;0000003C&nbsp;;恢复堆栈，回收本地变量的内存空间&nbsp;<br>:00401078&nbsp;C3&nbsp;ret&nbsp;000C&nbsp;;函数返回，恢复参数占用的内存空间&nbsp;<br>;如果是&#8220;__cdecl&#8221;的话，这里是&#8220;ret&#8221;，堆栈将由调用者恢复&nbsp;</p>
<p>;-------------------函数结束-------------------------&nbsp;</p>
<p><br>;--------------主程序调用func函数的代码--------------&nbsp;</p>
<p>:00401080&nbsp;6A03&nbsp;push&nbsp;00000003&nbsp;//压入参数param3&nbsp;<br>:00401082&nbsp;6A02&nbsp;push&nbsp;00000002&nbsp;//压入参数param2&nbsp;<br>:00401084&nbsp;6A01&nbsp;push&nbsp;00000001&nbsp;//压入参数param1&nbsp;<br>:00401086&nbsp;E875FFFFFF&nbsp;call&nbsp;00401000&nbsp;//调用func函数&nbsp;<br>;如果是&#8220;__cdecl&#8221;的话，将在这里恢复堆栈，&#8220;add&nbsp;esp,&nbsp;0000000C&#8221;&nbsp;</p>
<p>聪明的读者看到这里，差不多就明白缓冲溢出的原理了。先来看下面的代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;<br>#include&nbsp;&lt;string.h&gt;&nbsp;</p>
<p>void&nbsp;__stdcall&nbsp;func()&nbsp;<br>{&nbsp;<br>char&nbsp;lpBuff[8]="\0";&nbsp;<br>strcat(lpBuff,"AAAAAAAAAAA");&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>func();&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后执行一下回怎么样？哈，&#8220;"0x00414141"指令引用的"0x00000000"内存。该内存不能为"read"。&#8221;，&#8220;非法操作&#8221;喽！"41"就是"A"的16进制的ASCII码了，那明显就是strcat这句出的问题了。"lpBuff"的大小只有8字节，算进结尾的\0，那strcat最多只能写入7个"A"，但程序实际写入了11个"A"外加1个\0。再来看看上面那幅图，多出来的4个字节正好覆盖了RET的所在的内存空间，导致函数返回到一个错误的内存地址，执行了错误的指令。如果能精心构造这个字符串，使它分成三部分，前一部份仅仅是填充的无意义数据以达到溢出的目的，接着是一个覆盖RET的数据，紧接着是一段shellcode，那只要着个RET地址能指向这段shellcode的第一个指令，那函数返回时就能执行shellcode了。但是软件的不同版本和不同的运行环境都可能影响这段shellcode在内存中的位置，那么要构造这个RET是十分困难的。一般都在RET和shellcode之间填充大量的NOP指令，使得exploit有更强的通用性。&nbsp;</p>
<p><br>├———————┤&lt;—低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—由exploit填入数据的开始&nbsp;<br>│&nbsp;│&nbsp;<br>│&nbsp;buffer&nbsp;│&lt;—填入无用的数据&nbsp;<br>│&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;RET&nbsp;│&lt;—指向shellcode，或NOP指令的范围&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;NOP&nbsp;│&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&lt;—填入的NOP指令，是RET可指向的范围&nbsp;<br>│&nbsp;NOP&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;│&nbsp;<br>│&nbsp;shellcode&nbsp;│&nbsp;<br>│&nbsp;│&nbsp;<br>├———————┤&lt;—由exploit填入数据的结束&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—高端内存区域&nbsp;</p>
<p><br>windows下的动态数据除了可存放在栈中，还可以存放在堆中。了解C++的朋友都知道，C++可以使用new关键字来动态分配内存。来看下面的C++代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;<br>#include&nbsp;&lt;iostream.h&gt;&nbsp;<br>#include&nbsp;&lt;windows.h&gt;&nbsp;</p>
<p>void&nbsp;func()&nbsp;<br>{&nbsp;<br>char&nbsp;*buffer=new&nbsp;char[128];&nbsp;<br>char&nbsp;bufflocal[128];&nbsp;<br>static&nbsp;char&nbsp;buffstatic[128];&nbsp;<br>printf("0x%08x\n",buffer);&nbsp;//打印堆中变量的内存地址&nbsp;<br>printf("0x%08x\n",bufflocal);&nbsp;//打印本地变量的内存地址&nbsp;<br>printf("0x%08x\n",buffstatic);&nbsp;//打印静态变量的内存地址&nbsp;<br>}&nbsp;</p>
<p>void&nbsp;main()&nbsp;<br>{&nbsp;<br>func();&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>程序执行结果为：&nbsp;</p>
<p>0x004107d0&nbsp;<br>0x0012ff04&nbsp;<br>0x004068c0&nbsp;</p>
<p>可以发现用new关键字分配的内存即不在栈中，也不在静态数据区。VC编译器是通过windows下的&#8220;堆(heap)&#8221;来实现new关键字的内存动态分配。在讲&#8220;堆&#8221;之前，先来了解一下和&#8220;堆&#8221;有关的几个API函数：&nbsp;</p>
<p>HeapAlloc&nbsp;在堆中申请内存空间&nbsp;<br>HeapCreate&nbsp;创建一个新的堆对象&nbsp;<br>HeapDestroy&nbsp;销毁一个堆对象&nbsp;<br>HeapFree&nbsp;释放申请的内存&nbsp;<br>HeapWalk&nbsp;枚举堆对象的所有内存块&nbsp;<br>GetProcessHeap&nbsp;取得进程的默认堆对象&nbsp;<br>GetProcessHeaps&nbsp;取得进程所有的堆对象&nbsp;<br>LocalAlloc&nbsp;<br>GlobalAlloc&nbsp;</p>
<p>当进程初始化时，系统会自动为进程创建一个默认堆，这个堆默认所占内存的大小为1M。堆对象由系统进行管理，它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间：&nbsp;</p>
<p>HANDLE&nbsp;hHeap=GetProcessHeap();&nbsp;<br>char&nbsp;*buff=HeapAlloc(hHeap,0,8);&nbsp;</p>
<p>其中hHeap是堆对象的句柄，buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢？它的值有什么意义吗？看看下面这段代码吧：&nbsp;</p>
<p>#pragma&nbsp;comment(linker,"/entry:main")&nbsp;//定义程序的入口&nbsp;<br>#include&nbsp;&lt;windows.h&gt;&nbsp;</p>
<p>_CRTIMP&nbsp;int&nbsp;(__cdecl&nbsp;*printf)(const&nbsp;char&nbsp;*,&nbsp;...);&nbsp;//定义STL函数printf&nbsp;<br>/*---------------------------------------------------------------------------&nbsp;<br>写到这里，我们顺便来复习一下前面所讲的知识：&nbsp;<br>(*注)printf函数是C语言的标准函数库中函数，VC的标准函数库由msvcrt.dll模块实现。&nbsp;<br>由函数定义可见，printf的参数个数是可变的，函数内部无法预先知道调用者压入的参数个数，函数只能通过分析第一个参数字符串的格式来获得压入参数的信息，由于这里参数的个数是动态的，所以必须由调用者来平衡堆栈，这里便使用了__cdecl调用规则。BTW，Windows系统的API函数基本上是__stdcall调用形式，只有一个API例外，那就是wsprintf，它使用__cdecl调用规则，同printf函数一样，这是由于它的参数个数是可变的缘故。&nbsp;<br>---------------------------------------------------------------------------*/&nbsp;<br>void&nbsp;main()&nbsp;<br>{&nbsp;<br>HANDLE&nbsp;hHeap=GetProcessHeap();&nbsp;<br>char&nbsp;*buff=HeapAlloc(hHeap,0,0x10);&nbsp;<br>char&nbsp;*buff2=HeapAlloc(hHeap,0,0x10);&nbsp;<br>HMODULE&nbsp;hMsvcrt=LoadLibrary("msvcrt.dll");&nbsp;<br>printf=(void&nbsp;*)GetProcAddress(hMsvcrt,"printf");&nbsp;<br>printf("0x%08x\n",hHeap);&nbsp;<br>printf("0x%08x\n",buff);&nbsp;<br>printf("0x%08x\n\n",buff2);&nbsp;<br>}&nbsp;</p>
<p>执行结果为：&nbsp;</p>
<p>0x00130000&nbsp;<br>0x00133100&nbsp;<br>0x00133118&nbsp;</p>
<p>hHeap的值怎么和那个buff的值那么接近呢？其实hHeap这个句柄就是指向HEAP首部的地址。在进程的用户区存着一个叫PEB(进程环境块)的结构，这个结构中存放着一些有关进程的重要信息，其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址，而偏移0x90处存放了指向进程所有堆的地址列表的指针。windows有很多API都使用进程的默认堆来存放动态数据，如windows&nbsp;2000下的所有ANSI版本的函数都是在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的，同一时刻只能有一个线程访问堆中的数据，当多个线程同时有访问要求时，只能排队等待，这样便造成程序执行效率下降。&nbsp;</p>
<p>最后来说说内存中的数据对齐。所位数据对齐，是指数据所在的内存地址必须是该数据长度的整数倍，DWORD数据的内存起始地址能被4除尽，WORD数据的内存起始地址能被2除尽，x86&nbsp;CPU能直接访问对齐的数据，当他试图访问一个未对齐的数据时，会在内部进行一系列的调整，这些调整对于程序来说是透明的，但是会降低运行速度，所以编译器在编译程序时会尽量保证数据对齐。同样一段代码，我们来看看用VC、Dev-C++和lcc三个不同编译器编译出来的程序的执行结果：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>int&nbsp;a;&nbsp;<br>char&nbsp;b;&nbsp;<br>int&nbsp;c;&nbsp;<br>printf("0x%08x\n",&amp;a);&nbsp;<br>printf("0x%08x\n",&amp;b);&nbsp;<br>printf("0x%08x\n",&amp;c);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>这是用VC编译后的执行结果：&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff7b&nbsp;<br>0x0012ff80&nbsp;<br>变量在内存中的顺序：b(1字节)-a(4字节)-c(4字节)。&nbsp;</p>
<p>这是用Dev-C++编译后的执行结果：&nbsp;<br>0x0022ff7c&nbsp;<br>0x0022ff7b&nbsp;<br>0x0022ff74&nbsp;<br>变量在内存中的顺序：c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。&nbsp;</p>
<p>这是用lcc编译后的执行结果：&nbsp;<br>0x0012ff6c&nbsp;<br>0x0012ff6b&nbsp;<br>0x0012ff64&nbsp;<br>变量在内存中的顺序：同上。&nbsp;</p>
<p>三个编译器都做到了数据对齐，但是后两个编译器显然没VC&#8220;聪明&#8221;，让一个char占了4字节，浪费内存哦。&nbsp;</p>
<p><br>基础知识：&nbsp;<br>堆栈是一种简单的数据结构，是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶，另一端称为栈底，对堆栈的插入和删除操作被称为入栈和出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中，POP指令实现出栈操作，PUSH指令实现入栈操作。CPU的ESP寄存器存放当前线程的栈顶指针，EBP寄存器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址，当CPU执行完当前的指令后，从EIP寄存器中读取下一条指令的内存地址，然后继续执行。&nbsp;</p>
<p><br>参考：《Windows下的HEAP溢出及其利用》by:&nbsp;isno&nbsp;<br>《windows核心编程》by:&nbsp;Jeffrey&nbsp;Richter&nbsp;<br><br><br><br></p>
<p>摘要：&nbsp;讨论常见的堆性能问题以及如何防范它们。（共&nbsp;9&nbsp;页）</p>
<p>前言<br>您是否是动态分配的&nbsp;C/C++&nbsp;对象忠实且幸运的用户？您是否在模块间的往返通信中频繁地使用了&#8220;自动化&#8221;？您的程序是否因堆分配而运行起来很慢？不仅仅您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说，&#8220;我的代码真正好，只是堆太慢&#8221;。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题，是很有用的。</p>
<p>什么是堆？<br>（如果您已经知道什么是堆，可以跳到&#8220;什么是常见的堆性能问题？&#8221;部分）</p>
<p>在程序中，使用堆来动态分配和释放对象。在下列情况下，调用堆操作：&nbsp;</p>
<p>事先不知道程序所需对象的数量和大小。</p>
<p><br>对象太大而不适合堆栈分配程序。<br>堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。<br><a href="http://club.5ivb.net/UploadFile/2005311144027byUID16686.gif" target=_blank><img alt="" src="http://club.5ivb.net/UploadFile/2005311144027byUID16686.gif" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></a></p>
<p>GlobalAlloc/GlobalFree：Microsoft&nbsp;Win32&nbsp;堆调用，这些调用直接与每个进程的默认堆进行对话。</p>
<p>LocalAlloc/LocalFree：Win32&nbsp;堆调用（为了与&nbsp;Microsoft&nbsp;Windows&nbsp;NT&nbsp;兼容），这些调用直接与每个进程的默认堆进行对话。</p>
<p>COM&nbsp;的&nbsp;IMalloc&nbsp;分配程序（或&nbsp;CoTaskMemAlloc&nbsp;/&nbsp;CoTaskMemFree）：函数使用每个进程的默认堆。自动化程序使用&#8220;组件对象模型&nbsp;(COM)&#8221;的分配程序，而申请的程序使用每个进程堆。</p>
<p>C/C++&nbsp;运行时&nbsp;(CRT)&nbsp;分配程序：提供了&nbsp;malloc()&nbsp;和&nbsp;free()&nbsp;以及&nbsp;new&nbsp;和&nbsp;delete&nbsp;操作符。如&nbsp;Microsoft&nbsp;Visual&nbsp;Basic&nbsp;和&nbsp;Java&nbsp;等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT&nbsp;创建自己的私有堆，驻留在&nbsp;Win32&nbsp;堆的顶部。</p>
<p>Windows&nbsp;NT&nbsp;中，Win32&nbsp;堆是&nbsp;Windows&nbsp;NT&nbsp;运行时分配程序周围的薄层。所有&nbsp;API&nbsp;转发它们的请求给&nbsp;NTDLL。</p>
<p>Windows&nbsp;NT&nbsp;运行时分配程序提供&nbsp;Windows&nbsp;NT&nbsp;内的核心堆分配程序。它由具有&nbsp;128&nbsp;个大小从&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。</p>
<p>在图表的底部是&#8220;虚拟内存分配程序&#8221;，操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。</p>
<p>分配和释放块不就那么简单吗？为何花费这么长时间？</p>
<p>堆实现的注意事项<br>传统上，操作系统和运行时库是与堆的实现共存的。在一个进程的开始，操作系统创建一个默认堆，叫做&#8220;进程堆&#8221;。如果没有其他堆可使用，则块的分配使用&#8220;进程堆&#8221;。语言运行时也能在进程内创建单独的堆。（例如，C&nbsp;运行时创建它自己的堆。）除这些专用的堆外，应用程序或许多已载入的动态链接库&nbsp;(DLL)&nbsp;之一可以创建和使用单独的堆。Win32&nbsp;提供一整套&nbsp;API&nbsp;来创建和使用私有堆。有关堆函数（英文）的详尽指导，请参见&nbsp;MSDN。</p>
<p>当应用程序或&nbsp;DLL&nbsp;创建私有堆时，这些堆存在于进程空间，并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。（不能从一个堆分配而在另一个堆释放。）</p>
<p>在所有虚拟内存系统中，堆驻留在操作系统的&#8220;虚拟内存管理器&#8221;的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下，这些堆是操作系统堆中的层，而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆，而使用虚拟内存函数更利于堆的分配和块的使用。</p>
<p>典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用，堆尝试从前端列表找到一个自由块。如果失败，堆被迫从后端（保留和提交虚拟内存）分配一个大块来满足请求。通用的实现有每块分配的开销，这将耗费执行周期，也减少了可使用的存储空间。</p>
<p>Knowledge&nbsp;Base&nbsp;文章&nbsp;Q10758，&#8220;用&nbsp;calloc()&nbsp;和&nbsp;malloc()&nbsp;管理内存&#8221;&nbsp;（搜索文章编号）,&nbsp;包含了有关这些主题的更多背景知识。另外，有关堆实现和设计的详细讨论也可在下列著作中找到：&#8220;Dynamic&nbsp;Storage&nbsp;Allocation:&nbsp;A&nbsp;Survey&nbsp;and&nbsp;Critical&nbsp;Review&#8221;，作者&nbsp;Paul&nbsp;R.&nbsp;Wilson、Mark&nbsp;S.&nbsp;Johnstone、Michael&nbsp;Neely&nbsp;和&nbsp;David&nbsp;Boles；&#8220;International&nbsp;Workshop&nbsp;on&nbsp;Memory&nbsp;Management&#8221;,&nbsp;作者&nbsp;Kinross,&nbsp;Scotland,&nbsp;UK,&nbsp;1995&nbsp;年&nbsp;9&nbsp;月(<img src="http://club.5ivb.net/pic/url.gif" align=absMiddle border=0><a href="http://www.cs.utexas.edu/users/oops/papers.html" target=_blank><font color=#000000>http://www.cs.utexas.edu/users/oops/papers.html</font></a>)（英文）。</p>
<p>Windows&nbsp;NT&nbsp;的实现（Windows&nbsp;NT&nbsp;版本&nbsp;4.0&nbsp;和更新版本）&nbsp;使用了&nbsp;127&nbsp;个大小从&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节的&nbsp;8&nbsp;字节对齐块空闲列表和一个&#8220;大块&#8221;列表。&#8220;大块&#8221;列表（空闲列表[0]）&nbsp;保存大于&nbsp;1,024&nbsp;字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下，&#8220;进程堆&#8221;执行收集操作。（收集是将相邻空闲块合并成一个大块的操作。）收集耗费了额外的周期，但减少了堆块的内部碎片。</p>
<p>单一全局锁保护堆，防止多线程式的使用。（请参见&#8220;Server&nbsp;Performance&nbsp;and&nbsp;Scalability&nbsp;Killers&#8221;中的第一个注意事项,&nbsp;George&nbsp;Reilly&nbsp;所著，在&nbsp;&#8220;MSDN&nbsp;Online&nbsp;Web&nbsp;Workshop&#8221;上（站点：<img src="http://club.5ivb.net/pic/url.gif" align=absMiddle border=0><a href="http://msdn.microsoft.com/workshop/server/iis/tencom.asp" target=_blank><font color=#000000>http://msdn.microsoft.com/workshop/server/iis/tencom.asp</font></a>（英文）。）单一全局锁本质上是用来保护堆数据结构，防止跨多线程的随机存取。若堆操作太频繁，单一全局锁会对性能有不利的影响。</p>
<p>什么是常见的堆性能问题？<br>以下是您使用堆时会遇到的最常见问题：&nbsp;</p>
<p>分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块，所以运行时分配程序代码会耗费周期寻找较大的空闲块，或从后端分配程序分配新块。</p>
<p><br>释放操作造成的速度减慢。释放操作耗费较多周期，主要是启用了收集操作。收集期间，每个释放操作&#8220;查找&#8221;它的相邻块，取出它们并构造成较大块，然后再把此较大块插入空闲列表。在查找期间，内存可能会随机碰到，从而导致高速缓存不能命中，性能降低。</p>
<p><br>堆竞争造成的速度减慢。当两个或多个线程同时访问数据，而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦；这也是目前多处理器系统遇到的最大问题。当大量使用内存块的应用程序或&nbsp;DLL&nbsp;以多线程方式运行（或运行于多处理器系统上）时将导致速度减慢。单一锁定的使用—常用的解决方案—意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。&nbsp;<br>竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的，但开销更大的是数据从处理器高速缓存中丢失，以及后来线程复活时的数据重建。</p>
<p>堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块，以及块的越界重写等明显问题。（破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节，请参见&nbsp;Microsoft&nbsp;Visual&nbsp;C++(R)&nbsp;调试文档&nbsp;。）</p>
<p><br>频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配，随重分配增长和释放。不要这样做，如果可能，尽量分配大字符串和使用缓冲区。另一种方法就是尽量少用连接操作。<br>竞争是在分配和释放操作中导致速度减慢的问题。理想情况下，希望使用没有竞争和快速分配/释放的堆。可惜，现在还没有这样的通用堆，也许将来会有。</p>
<p>在所有的服务器系统中（如&nbsp;IIS、MSProxy、DatabaseStacks、网络服务器、&nbsp;Exchange&nbsp;和其他）,&nbsp;堆锁定实在是个大瓶颈。处理器数越多，竞争就越会恶化。</p>
<p>尽量减少堆的使用<br>现在您明白使用堆时存在的问题了，难道您不想拥有能解决这些问题的超级魔棒吗？我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略，情况将会大大好转。调整使用堆的方法，减少对堆的操作是提高性能的良方。</p>
<p>如何减少使用堆操作？通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例：</p>
<p>struct&nbsp;ObjectA&nbsp;{<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectA&nbsp;的数据&nbsp;<br>}</p>
<p>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//&nbsp;同时使用&nbsp;objectA&nbsp;和&nbsp;objectB</p>
<p>//<br>//&nbsp;使用指针&nbsp;<br>//<br>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;*&nbsp;pObjA;<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//<br>//&nbsp;使用嵌入<br>//<br>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;pObjA;<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//<br>//&nbsp;集合&nbsp;&#8211;&nbsp;在另一对象内使用&nbsp;objectA&nbsp;和&nbsp;objectB<br>//</p>
<p>struct&nbsp;ObjectX&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;&nbsp;objA;<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectB&nbsp;&nbsp;objB;<br>}</p>
<p>避免使用指针关联两个数据结构。如果使用指针关联两个数据结构，前面实例中的对象&nbsp;A&nbsp;和&nbsp;B&nbsp;将被分别分配和释放。这会增加额外开销—我们要避免这种做法。</p>
<p><br>把带指针的子对象嵌入父对象。当对象中有指针时，则意味着对象中有动态元素（百分之八十）和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。</p>
<p><br>合并小对象形成大对象（聚合）。聚合减少分配和释放的块的数量。如果有几个开发者，各自开发设计的不同部分，则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。</p>
<p><br>内联缓冲区能够满足百分之八十的需要（aka&nbsp;80-20&nbsp;规则）。个别情况下，需要内存缓冲区来保存字符串/二进制数据，但事先不知道总字节数。估计并内联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十，可以分配一个新的缓冲区和指向这个缓冲区的指针。这样，就减少分配和释放调用并增加数据的位置空间，从根本上提高代码的性能。</p>
<p><br>在块中分配对象（块化）。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪，例如对一个&nbsp;{名称，值}&nbsp;对的列表，有两种选择：选择一是为每一个&#8220;名称-值&#8221;对分配一个节点；选择二是分配一个能容纳（如五个）&#8220;名称-值&#8221;对的结构。例如，一般情况下，如果存储四对，就可减少节点的数量，如果需要额外的空间数量，则使用附加的链表指针。&nbsp;<br>块化是友好的处理器高速缓存，特别是对于&nbsp;L1-高速缓存，因为它提供了增加的位置&nbsp;—不用说对于块分配，很多数据块会在同一个虚拟页中。</p>
<p>正确使用&nbsp;_amblksiz。C&nbsp;运行时&nbsp;(CRT)&nbsp;有它的自定义前端分配程序，该分配程序从后端（Win32&nbsp;堆）分配大小为&nbsp;_amblksiz&nbsp;的块。将&nbsp;_amblksiz&nbsp;设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用&nbsp;CRT&nbsp;的程序适用。<br>使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面，代码会有点特殊，但如果经过深思熟虑，代码还是很容易管理的。</p>
<p>其他提高性能的技术<br>下面是一些提高速度的技术：&nbsp;</p>
<p>使用&nbsp;Windows&nbsp;NT5&nbsp;堆&nbsp;<br>由于几个同事的努力和辛勤工作，1998&nbsp;年初&nbsp;Microsoft&nbsp;Windows(R)&nbsp;2000&nbsp;中有了几个重大改进：</p>
<p>改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构，防止多线程式的使用。但不幸的是，在高通信量的情况下，堆仍受困于全局锁，导致高竞争和低性能。Windows&nbsp;2000&nbsp;中，锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。</p>
<p><br>使用&nbsp;&#8220;Lookaside&#8221;列表。堆数据结构对块的所有空闲项使用了大小在&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节（以&nbsp;8-字节递增）的快速高速缓存。快速高速缓存最初保护在全局锁内。现在，使用&nbsp;lookaside&nbsp;列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定，而是使用&nbsp;64&nbsp;位的互锁操作，因此提高了性能。</p>
<p><br>内部数据结构算法也得到改进。<br>这些改进避免了对分配高速缓存的需求，但不排除其他的优化。使用&nbsp;Windows&nbsp;NT5&nbsp;堆评估您的代码；它对小于&nbsp;1,024&nbsp;字节&nbsp;(1&nbsp;KB)&nbsp;的块（来自前端分配程序的块）是最佳的。GlobalAlloc()&nbsp;和&nbsp;LocalAlloc()&nbsp;建立在同一堆上，是存取每个进程堆的通用机制。如果希望获得高的局部性能，则使用&nbsp;Heap(R)&nbsp;API&nbsp;来存取每个进程堆，或为分配操作创建自己的堆。如果需要对大块操作，也可以直接使用&nbsp;VirtualAlloc()&nbsp;/&nbsp;VirtualFree()&nbsp;操作。</p>
<p>上述改进已在&nbsp;Windows&nbsp;2000&nbsp;beta&nbsp;2&nbsp;和&nbsp;Windows&nbsp;NT&nbsp;4.0&nbsp;SP4&nbsp;中使用。改进后，堆锁的竞争率显著降低。这使所有&nbsp;Win32&nbsp;堆的直接用户受益。CRT&nbsp;堆建立于&nbsp;Win32&nbsp;堆的顶部，但它使用自己的小块堆，因而不能从&nbsp;Windows&nbsp;NT&nbsp;改进中受益。（Visual&nbsp;C++&nbsp;版本&nbsp;6.0&nbsp;也有改进的堆分配程序。）</p>
<p>使用分配高速缓存&nbsp;<br>分配高速缓存允许高速缓存分配的块，以便将来重用。这能够减少对进程堆（或全局堆）的分配/释放调用的次数，也允许最大限度的重用曾经分配的块。另外，分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。</p>
<p>典型地，自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套固定大小（如&nbsp;32&nbsp;字节、64&nbsp;字节、128&nbsp;字节等）。这一个很好的策略，但这种自定义堆分配程序丢失与分配和释放的对象相关的&#8220;语义信息&#8221;。&nbsp;</p>
<p>与自定义堆分配程序相反，&#8220;分配高速缓存&#8221;作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外，它们还能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化，这些参数表示并发级别、对象大小和保持在空闲列表中的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池（不超过指定的阀值）并使用私有保护锁。合在一起，分配高速缓存和私有锁减少了与主系统堆的通信量，因而提供了增加的并发、最大限度的重用和较高的可伸缩性。</p>
<p>需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动，将释放分配对象的池，从而提高性能。</p>
<p>可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一，这种关系可以用来减少内存分配。</p>
<p>分配高速缓存也起到了调试助手的作用，帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名，甚至能够找到确切的失败的调用者。</p>
<p>MP&nbsp;堆&nbsp;<br>MP&nbsp;堆是对多处理器友好的分布式分配的程序包，在&nbsp;Win32&nbsp;SDK（Windows&nbsp;NT&nbsp;4.0&nbsp;和更新版本）中可以得到。最初由&nbsp;JVert&nbsp;实现，此处堆抽象建立在&nbsp;Win32&nbsp;堆程序包的顶部。MP&nbsp;堆创建多个&nbsp;Win32&nbsp;堆，并试图将分配调用分布到不同堆，以减少在所有单一锁上的竞争。</p>
<p>本程序包是好的步骤&nbsp;—一种改进的&nbsp;MP-友好的自定义堆分配程序。但是，它不提供语义信息和缺乏统计功能。通常将&nbsp;MP&nbsp;堆作为&nbsp;SDK&nbsp;库来使用。如果使用这个&nbsp;SDK&nbsp;创建可重用组件，您将大大受益。但是，如果在每个&nbsp;DLL&nbsp;中建立这个&nbsp;SDK&nbsp;库，将增加工作设置。</p>
<p>重新思考算法和数据结构&nbsp;<br>要在多处理器机器上伸缩，则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问，&#8220;我能用不同的数据结构完成此工作吗？&#8221;例如，如果在应用程序初始化时加载了只读项的列表，这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片，从而增强性能。</p>
<p>减少需要的小对象的数量减少堆分配程序的负载。例如，我们在服务器的关键处理路径上使用五个不同的对象，每个对象单独分配和释放。一起高速缓存这些对象，把堆调用从五个减少到一个，显著减少了堆的负载，特别当每秒钟处理&nbsp;1,000&nbsp;个以上的请求时。</p>
<p>如果大量使用&#8220;Automation&#8221;结构，请考虑从主线代码中删除&#8220;Automation&nbsp;BSTR&#8221;，或至少避免重复的&nbsp;BSTR&nbsp;操作。（BSTR&nbsp;连接导致过多的重分配和分配/释放操作。）</p>
<p>摘要<br>对所有平台往往都存在堆实现，因此有巨大的开销。每个单独代码都有特定的要求，但设计能采用本文讨论的基本理论来减少堆之间的相互作用。&nbsp;</p>
<p>评价您的代码中堆的使用。</p>
<p><br>改进您的代码，以使用较少的堆调用：分析关键路径和固定数据结构。</p>
<p><br>在实现自定义的包装程序之前使用量化堆调用成本的方法。</p>
<p><br>如果对性能不满意，请要求&nbsp;OS&nbsp;组改进堆。更多这类请求意味着对改进堆的更多关注。</p>
<p><br>要求&nbsp;C&nbsp;运行时组针对&nbsp;OS&nbsp;所提供的堆制作小巧的分配包装程序。随着&nbsp;OS&nbsp;堆的改进，C&nbsp;运行时堆调用的成本将减小。</p>
<p><br>操作系统（Windows&nbsp;NT&nbsp;家族）正在不断改进堆。请随时关注和利用这些改进。<br>Murali&nbsp;Krishnan&nbsp;是&nbsp;Internet&nbsp;Information&nbsp;Server&nbsp;(IIS)&nbsp;组的首席软件设计工程师。从&nbsp;1.0&nbsp;版本开始他就设计&nbsp;IIS，并成功发行了&nbsp;1.0&nbsp;版本到&nbsp;4.0&nbsp;版本。Murali&nbsp;组织并领导&nbsp;IIS&nbsp;性能组三年&nbsp;(1995-1998),&nbsp;从一开始就影响&nbsp;IIS&nbsp;性能。他拥有威斯康星州&nbsp;Madison&nbsp;大学的&nbsp;M.S.和印度&nbsp;Anna&nbsp;大学的&nbsp;B.S.。工作之外，他喜欢阅读、打排球和家庭烹饪。<br><br><br><br><img src="http://club.5ivb.net/pic/url.gif" align=absMiddle border=0><a href="http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835" target=_blank><font color=#000000>http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835</font></a><br>我在学习对象的生存方式的时候见到一种是在堆栈(stack)之中，如下&nbsp;&nbsp;<br>CObject&nbsp;&nbsp;object;&nbsp;&nbsp;<br>还有一种是在堆(heap)中&nbsp;&nbsp;如下&nbsp;&nbsp;<br>CObject*&nbsp;&nbsp;pobject=new&nbsp;&nbsp;CObject();&nbsp;&nbsp;<br>&nbsp;<br>请问&nbsp;&nbsp;<br>（1）这两种方式有什么区别？&nbsp;&nbsp;<br>（2）堆栈与堆有什么区别？？&nbsp;&nbsp;<br>&nbsp;<br>&nbsp;<br>---------------------------------------------------------------&nbsp;&nbsp;<br>&nbsp;<br>1)&nbsp;&nbsp;about&nbsp;&nbsp;stack,&nbsp;&nbsp;system&nbsp;&nbsp;will&nbsp;&nbsp;allocate&nbsp;&nbsp;memory&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;instance&nbsp;&nbsp;of&nbsp;&nbsp;object&nbsp;&nbsp;automatically,&nbsp;&nbsp;and&nbsp;&nbsp;to&nbsp;&nbsp;the <br>&nbsp;heap,&nbsp;&nbsp;you&nbsp;&nbsp;must&nbsp;&nbsp;allocate&nbsp;&nbsp;memory&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;instance&nbsp;&nbsp;of&nbsp;&nbsp;object&nbsp;&nbsp;with&nbsp;&nbsp;new&nbsp;&nbsp;or&nbsp;&nbsp;malloc&nbsp;&nbsp;manually.&nbsp;&nbsp;<br>2)&nbsp;&nbsp;when&nbsp;&nbsp;function&nbsp;&nbsp;ends,&nbsp;&nbsp;system&nbsp;&nbsp;will&nbsp;&nbsp;automatically&nbsp;&nbsp;free&nbsp;&nbsp;the&nbsp;&nbsp;memory&nbsp;&nbsp;area&nbsp;&nbsp;of&nbsp;&nbsp;stack,&nbsp;&nbsp;but&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp; <br>heap,&nbsp;&nbsp;you&nbsp;&nbsp;must&nbsp;&nbsp;free&nbsp;&nbsp;the&nbsp;&nbsp;memory&nbsp;&nbsp;area&nbsp;&nbsp;manually&nbsp;&nbsp;with&nbsp;&nbsp;free&nbsp;&nbsp;or&nbsp;&nbsp;delete,&nbsp;&nbsp;else&nbsp;&nbsp;it&nbsp;&nbsp;will&nbsp;&nbsp;result&nbsp;&nbsp;in&nbsp;&nbsp;memory <br>leak.&nbsp;&nbsp;<br>3)栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。&nbsp;&nbsp;<br>4）堆上分配的内存可以有我们自己决定，使用非常灵活。&nbsp;&nbsp;<br>---------------------------------------------------------------&nbsp;&nbsp;</p>
</div>
</div>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56631.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-23 11:12 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/23/56631.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>挺进前三甲</title><link>http://www.cnitblog.com/liaoqingshan/archive/2009/04/21/56526.html</link><dc:creator>游子</dc:creator><author>游子</author><pubDate>Tue, 21 Apr 2009 02:23:00 GMT</pubDate><guid>http://www.cnitblog.com/liaoqingshan/archive/2009/04/21/56526.html</guid><wfw:comment>http://www.cnitblog.com/liaoqingshan/comments/56526.html</wfw:comment><comments>http://www.cnitblog.com/liaoqingshan/archive/2009/04/21/56526.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/liaoqingshan/comments/commentRss/56526.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/liaoqingshan/services/trackbacks/56526.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 12pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;很久都没有更新博客了，出差回来，发现我的博客一下子从第6名越到了第3名。这得归功于一位叫&#8220;小鱼儿&#8221;的好姑娘，把我的博客几乎看了个遍！实现了我多年的愿望！<img src="http://www.cnitblog.com/CuteSoft_Client/CuteEditor/images/emteeth.gif" align=absMiddle border=0><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;谢谢你啦！<img height=18 src="http://www.cnitblog.com/Emoticons/QQ/geek.gif" width=18 border=0><br><br>3.&nbsp;</span><a href="http://www.cnitblog.com/liaoqingshan/"><u><span style="FONT-SIZE: 12pt">游子</span></u></a><span style="FONT-SIZE: 12pt">&nbsp;</span><a class=BlogRss href="http://www.cnitblog.com/liaoqingshan/rss.aspx"><u><span style="FONT-SIZE: 12pt">(rss)</span></u></a><br><small><font style="FONT-SIZE: 18pt" size=1>(276,04-21 10:15,138011)</font></small>
<img src ="http://www.cnitblog.com/liaoqingshan/aggbug/56526.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/liaoqingshan/" target="_blank">游子</a> 2009-04-21 10:23 <a href="http://www.cnitblog.com/liaoqingshan/archive/2009/04/21/56526.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>