﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-hacrwang</title><link>http://www.cppblog.com/hacrwang/</link><description>this is temp storage for hacrwang</description><language>zh-cn</language><lastBuildDate>Thu, 23 Apr 2026 10:09:10 GMT</lastBuildDate><pubDate>Thu, 23 Apr 2026 10:09:10 GMT</pubDate><ttl>60</ttl><item><title>用CodeViz绘制函数调用关系图(call graph) </title><link>http://www.cppblog.com/hacrwang/archive/2007/06/30/27296.html</link><dc:creator>hacrwang</dc:creator><author>hacrwang</author><pubDate>Sat, 30 Jun 2007 12:23:00 GMT</pubDate><guid>http://www.cppblog.com/hacrwang/archive/2007/06/30/27296.html</guid><description><![CDATA[<p>CodeViz是《<a href="http://www.phptr.com/title/0131453483"><font color=#000080>Understanding The Linux Virtual Memory Manager</font></a>》(<a href="http://www.amazon.com/exec/obidos/tg/detail/-/0131453483/002-9292664-2255245"><font color=#000080>at Amazon</font></a>，<a href="http://www.phptr.com/promotion/1484"><font color=#000080>下载地址</font></a>在页尾)的作者 <a href="http://www.skynet.ie/~mel/"><font color=#000080>Mel Gorman</font></a> 写的一款分析C/C++源代码中函数调用关系的open source工具（类似的open source软件有 <a href="http://www.gson.org/egypt/egypt.html"><font color=#000080>egypt</font></a>、<a href="http://students.ceid.upatras.gr/~sxanth/ncc/"><font color=#000080>ncc</font></a>）。其基本原理是给 GCC 打个补丁，让它在编译时每个源文件时 dump 出其中函数的 call graph，然后用 Perl 脚本收集并整理调用关系，转交给<a href="http://www.graphviz.org/"><font color=#000080>Graphviz</font></a>绘制图形。</p>
<p>CodeViz 原本是作者用来分析 Linux virtual memory 的源码时写的一个小工具，现在已经基本支持 C++ 语言，最新的 1.0.9 版能在 Windows + Cygwin 下顺利地编译使用:)。需要注意的是：1) 下载 GCC 3.4.1 的源码 <a href="ftp://ftp.gnu.org/pub/gnu/gcc/gcc-3.4.1/gcc-3.4.1.tar.gz"><font color=#000080>gcc-3.4.1.tar.gz</font></a> 放到 codeviz-1.0.9/compilers，2) 安装 patch 程序（属于Utils类），3) 从 <a href="http://www.graphviz.org/"><font color=#000080>http://www.graphviz.org</font></a> 下载并安装 Graphviz 2.6。</p>
<p>我用 CodeViz 分析《嵌入式实时操作系统 uC/OS-II (第二版)》中的第一个范例程序，步骤如下：</p>
<p>1. 想办法让 gcc 能编译uC/OS 2.52和范例程序的源码，每个C源文件生成对于的.c.cdepn文件。只要编译(参数<code> -c</code>)就行，无需连接。</p>
<p>2. 调用genfull生成full.graph，这个文件记录了所有函数在源码中的位置和它们之间的调用关系。</p>
<p>3. 使用gengraph生成我关心的函数的调用关系。</p>
<p><font size=4>首先分析main()：</font></p>
<p>1. <code><strong>gengraph --output-type gif -f main</strong></code><br>分析main()的call graph，得到的图如下，看不出要领：<br><a href="http://blog.csdn.net/images/blog_csdn_net/Solstice/main1.gif" target=_blank><img height=562 alt="" src="http://blog.csdn.net/images/blog_csdn_net/Solstice/main1.gif" width=720></a></p>
<p>2. <code>gengraph --output-type gif -f main -s OSInit</code><br>暂时不关心OSInit()的内部实现细节(参数<code> -s</code>)，让它显示为一个节点。得到的图如下，有点乱，不过好多了：<br><a href="http://blog.csdn.net/images/blog_csdn_net/Solstice/main2.gif"><img alt="" src="http://blog.csdn.net/images/blog_csdn_net/Solstice/main2.gif" width=720></a></p>
<p>3. <code>gengraph --output-type gif -f main -s OSInit -i "OSCPUSaveSR;OSCPURestoreSR"</code><br>基本上每个函数都会有进入/退出临界区的代码，忽略之(参数<code> -i</code>)。得到的图如下，基本清楚了：<br><a href="http://blog.csdn.net/images/blog_csdn_net/Solstice/main3.gif"><img alt="" src="http://blog.csdn.net/images/blog_csdn_net/Solstice/main3.gif" width=720></a></p>
<p>4. <code><strong>gengraph --output-type gif -f main -s "OSInit;OSSemCreate" -i "OSCPUSaveSR;OSCPURestoreSR" -k</strong></code><br>OSSemCreate()的内部细节似乎也不用关心，不过保留中间文件sub.graph(参数<code> -k</code>)，得到的图如下，<br><a href="http://blog.csdn.net/images/blog_csdn_net/Solstice/main4.gif"><img alt="" src="http://blog.csdn.net/images/blog_csdn_net/Solstice/main4.gif" width=720></a></p>
<p>5. <code><strong>dot -Tgif -o main.gif sub.graph</strong></code><br>修改sub.graph，使图形符合函数调用顺序，最后得到的图如下，有了这个都不用看代码了:)<br><img alt="" src="http://blog.csdn.net/images/blog_csdn_net/Solstice/main5.gif"></p>
<p><font size=4>接着分析OSTimeDly()的被调用关系</font>：</p>
<p><span class=Code>gengraph --output-type gif -r -f OSTimeDly</span><br><br>看看哪些函数调用了OSTimeDly()，参数 -r ，Task()和TaskStart()都是用户编写的函数：</p>
<p><img height=277 alt="" src="http://blog.csdn.net/images/blog_csdn_net/Solstice/timedly.gif" width=509></p>
<p><font size=4>最后看看Task()直接调用了哪些函数：</font></p>
<p><span class=Code>gengraph --output-type gif -d 1 -f Task</span></p>
<p>只看从Task出发的第一层调用（参数 -d 1）：</p>
<p>&#160;</p>
<p><img height=349 alt="" src="http://blog.csdn.net/images/blog_csdn_net/Solstice/task2.gif" width=272></p>
<p>在分析源码的时候，把这些图形打印在手边，在上面做笔记，实在方便得很。</p>
<img src ="http://www.cppblog.com/hacrwang/aggbug/27296.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hacrwang/" target="_blank">hacrwang</a> 2007-06-30 20:23 <a href="http://www.cppblog.com/hacrwang/archive/2007/06/30/27296.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分析函数调用关系图(call graph)的几种方法 </title><link>http://www.cppblog.com/hacrwang/archive/2007/06/30/27295.html</link><dc:creator>hacrwang</dc:creator><author>hacrwang</author><pubDate>Sat, 30 Jun 2007 12:20:00 GMT</pubDate><guid>http://www.cppblog.com/hacrwang/archive/2007/06/30/27295.html</guid><description><![CDATA[<p>绘制函数调用关系图对理解大型程序大有帮助。我想大家都有过一边读源码（并在头脑中维护一个调用栈），一边在纸上画函数调用关系，然后整理成图的经历。如果运气好一点，借助调试器的单步跟踪功能和call stack窗口，能节约一些脑力。不过如果要分析的是脚本语言的代码，那多半只好老老实实用第一种方法了。如果在读代码之前，手边就有一份调用图，岂不妙哉？下面举出我知道的几种免费的分析C/C++函数调用关系的工具。</p>
<p>函数调用关系图（call graph）是图（graph），而且是有向图，多半还是无环图（无圈图）——如果代码中没有直接或间接的递归的话。<a href="http://www.graphviz.org/"><u><font color=#800080>Graphviz</font></u></a>是专门绘制有向图和无向图的工具，所以很多call graph分析工具都以它为后端(back end)。那么前端呢？就看各家各显神通了。</p>
<p>调用图的分析分析大致可分为&#8220;静态&#8221;和&#8220;动态&#8221;两种，所谓静态分析是指在不运行待分析的程序的前提下进行分析，那么动态分析自然就是记录程序实际运行时的函数调用情况了。</p>
<p><font size=4>静态分析</font>又有两种方法，一是分析源码，二是分析编译后的目标文件。</p>
<p>分析源码获得的调用图的质量取决于分析工具对编程语言的理解程度，比如能不能找出正确的C++重载函数。<a href="http://www.stack.nl/~dimitri/doxygen/index.html"><u><font color=#0000ff>Doxygen</font></u></a>是源码文档化工具，也能绘制调用图，它似乎是自己分析源码获得函数调用关系的。<a href="http://www.gnu.org/software/cflow/"><u><font color=#0000ff>GNU cflow</font></u></a>也是类似的工具，不过它似乎偏重分析流程图（flowchart）。</p>
<p>对编程语言的理解程度最好的当然是编译器了，所以有人想出给编译器打补丁，让它在编译时顺便记录函数调用关系。<a href="http://www.csn.ul.ie/~mel/projects/codeviz/"><u><font color=#0000ff>CodeViz</font></u></a><!--startfragment -->（其灵感来自<a href="http://luxik.cdi.cz/~devik/mm.htm"><u><font color=#0000ff>Martin Devera (Devik) 的工具</font></u></a>）就属于此类，它（1.0.9版）给GCC 3.4.1打了个补丁。另外一个工具<a href="http://www.gson.org/egypt/egypt.html"><u><font color=#0000ff>egypt</font></u></a>的思路更巧妙，不用大动干戈地给编译器打补丁，而是让编译器自己dump出调用关系，然后分析分析，交给Graphviz去绘图。不过也有人另起炉灶，自己写个C语言编译器（<a href="http://students.ceid.upatras.gr/~sxanth/ncc/"><u><font color=#0000ff>ncc</font></u></a>），专门分析调用图，勇气可嘉。不如要是对C++语言也这么干，成本不免太高了。分析C++的调用图，还是借助编译器比较实在。</p>
<p>分析目标文件听起来挺高深，其实不然，反汇编的工作交给binutils的objdump去做，只要分析一下反汇编出来的文本文件就行了。下面是Cygwin下objdump -d a.exe的部分结果：</p>
<p><span class=Code>00401050 &lt;_main&gt;:<br>&nbsp; 401050:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 55&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp; %ebp<br>&nbsp; 401051:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 89 e5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp; %esp,%ebp<br>&nbsp; 401053:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 83 ec 18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sub&nbsp;&nbsp;&nbsp; $0x18,%esp<br>&nbsp;&nbsp; ......<br></span><span class=Code>&nbsp;40107a:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c7 44 24 04 00 20 40&nbsp;&nbsp;&nbsp; movl&nbsp;&nbsp; $0x402000,0x4(%esp)<br>&nbsp; 401081:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00<br>&nbsp; 401082:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c7 04 24 02 20 40 00&nbsp;&nbsp;&nbsp; movl&nbsp;&nbsp; $0x402002,(%esp)<br>&nbsp; 401089:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e8 f2 00 00 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; call&nbsp;&nbsp; 401180 &lt;_fopen&gt;</span></p>
<p>从中可以看出，main()调用了fopen()。CodeViz带有分析目标文件的功能。</p>
<p><font size=4>动态分析</font>是在程序运行时记录函数的调用，然后整理成调用图。与静态分析相比，它能获得更多的信息，比如函数调用的先后顺序和次数；不过也有一定的缺点，比如程序中语句的某些分支可能没有执行到，这些分支中调用的函数自然就没有记录下来。</p>
<p>动态分析也有两种方法，一是借助gprof的call graph功能（参数<span class=Code>-q</span>），二是利用GCC的<!--startfragment --> <code>-finstrument-functions</code> 参数。</p>
<p>gprof生成的输出如下：</p>
<p><span class=Code>index % time&nbsp;&nbsp;&nbsp; self&nbsp; children&nbsp;&nbsp;&nbsp; called&nbsp;&nbsp;&nbsp;&nbsp; name<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4/4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foo [4]<br>[3]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.0&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bar [3]<br>-----------------------------------------------<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1/2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; init [5]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1/2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; main [45]<br>[4]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.0&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foo [4]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4/4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bar [3]<br>-----------------------------------------------<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1/1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; main [45]<br>[5]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.0&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; init [5]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp; 0.00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1/2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foo [4]<br>-----------------------------------------------</span></p>
<p>从中可以看出，bar()被foo()调用了4次，foo()被init()和main()各调用了一次，init()被main()调用了一次。用Perl脚本分析gprof的输出，生成Graphviz的dot输入，就能绘制call graph了。这样的脚本不止一个人写过：<a href="http://www.graphviz.org/Resources.php"><u><font color=#0000ff>http://www.graphviz.org/Resources.php</font></u></a>，<a href="http://www.ioplex.com/~miallen/"><u><font color=#0000ff>http://www.ioplex.com/~miallen/</font></u></a>。</p>
<p>GCC的<font face="Courier New">-finstrument-functions</font> 参数的作用是在程序中加入hook，让它在每次进入和退出函数的时候分别调用下面这两个函数：</p>
<p><code>void __cyg_profile_func_enter( void *func_address, void *call_site )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __attribute__ ((no_instrument_function));<br><br>void __cyg_profile_func_exit ( void *func_address, void *call_site )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __attribute__ ((no_instrument_function));</code></p>
<p>当然，这两个函数本身不能被钩住（使用no_instrument_function这个__attribute__），不然就反反复复万世不竭了:) 这里获得的是函数地址，需要用binutils中的addr2line这个小工具转换为函数名，如果是C++函数，还要用c++filt进行name demangle。具体方法在《
<td></td>
<span class=atitle><a href="http://www-128.ibm.com/developerworks/cn/linux/l-graphvis/"><u><font color=#0000ff>用Graphviz 可视化函数调用</font></u></a></span>》中有详细介绍，这里不再赘述。</p>
<p>从适应能力上看，源码分析法是最强的，即便源码中有语法错，头文件不全也没关系，它照样能分析个八九不离十。而基于编译器的分析法对源码的要求要高一些，至少能编译通过（gcc 参数 -c）——能产生object file，不一定要链接得到可执行文件。这至少要求源码没有语法错，其中调用的函数不一定有定义(definition)，但要有声明(declaration)，也就是说头文件要齐全。当然，真的不全也没关系，自己放几个函数声明在前面就能糊弄编译器:) 至于动态分析，要求最高——程序需得运行起来。如果你要分析的是操作系统中某一部分，比如内存管理或网络协议栈，那么这里提到的两种动态分析法恐怕都不适用了。</p>
<p>我发现前面列举的所有免费工具几乎都和GCC、GNU Binutils脱不了干系。这里在把它们整理一下，用Graphviz绘成图：</p>
<p><img height=392 alt="" src="http://blog.csdn.net/images/blog_csdn_net/Solstice/call.gif" width=981></p>
<br>
<img src ="http://www.cppblog.com/hacrwang/aggbug/27295.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hacrwang/" target="_blank">hacrwang</a> 2007-06-30 20:20 <a href="http://www.cppblog.com/hacrwang/archive/2007/06/30/27295.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>nmake 的 batch mode (批模式) 推理规则可以用一次命令行调用编译多个文件 </title><link>http://www.cppblog.com/hacrwang/archive/2007/06/30/27294.html</link><dc:creator>hacrwang</dc:creator><author>hacrwang</author><pubDate>Sat, 30 Jun 2007 12:19:00 GMT</pubDate><guid>http://www.cppblog.com/hacrwang/archive/2007/06/30/27294.html</guid><description><![CDATA[<p>在一般的推理规则下，把 5 个 .cpp 文件编译为 5 个 .obj 文件需要调用 5 次 cl.exe，而 Microsoft nmake 支持 batch mode (批模式) 推理规则，只需调用一次 cl.exe 就能编译出这 5 个 .obj 文件，加快编译速度。</p>
<p>举例来说，有如下 Makefile:</p>
<p><font face="Courier New"># start of Makefile<br><br>all: foo1.obj foo2.obj foo3.obj foo4.obj<br><br>.cpp.obj:<br>&nbsp;&nbsp;&nbsp; cl /nologo /c $&lt;<br><br># end of Makefile</font></p>
<p><font face="Courier New">那么执行 nmake 得到的输出是</font></p>
<p><font face="Courier New">Microsoft (R) Program Maintenance Utility Version 8.00.50727.762<br>Copyright (C) Microsoft Corporation.&nbsp; All rights reserved.</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cl /nologo /c <font color=#0000ff>foo1.cpp<br></font>foo1.cpp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cl /nologo /c <font color=#0000ff>foo2.cpp</font><br>foo2.cpp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cl /nologo /c <font color=#0000ff>foo3.cpp</font><br>foo3.cpp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cl /nologo /c <font color=#0000ff>foo4.cpp</font><br>foo4.cpp</font></p>
<p>可见期间调用了 4 次 CL。如果把上述 Makefile 中的&#8220;<font face="Courier New">.cpp.obj:&#8221;替换为&#8220;.cpp.obj:<font color=#ff0000>:</font>&#8221;，注意多了一个冒号，再调用 nmake 得到的输出是</font></p>
<p><font face="Courier New">Microsoft (R) Program Maintenance Utility Version 8.00.50727.762<br>Copyright (C) Microsoft Corporation.&nbsp; All rights reserved.</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cl /nologo /c <font color=#0000ff>foo1.cpp foo2.cpp foo3.cpp foo4.cpp<br></font>foo1.cpp<br>foo2.cpp<br>foo3.cpp<br>foo4.cpp<br>Generating Code...</font></p>
<p>可见这样一次就把 4 个文件都编译出来了。只要多加一个冒号，就能节省反复启动编译器的时间，加快编译速度。</p>
<p>在预定义的规则中，nmake 是开启批模式的，可以用命令行参数 /Y 来禁用它。</p>
<p><a href="http://msdn2.microsoft.com/zh-cn/library/f2x0zs74(VS.80).aspx"><u><font color=#0000ff>http://msdn2.microsoft.com/zh-cn/library/f2x0zs74(VS.80).aspx</font></u></a>&nbsp;（批模式规则）</p>
<p><a href="http://msdn2.microsoft.com/zh-cn/library/cx06ysxh(VS.80).aspx"><u><font color=#0000ff>http://msdn2.microsoft.com/zh-cn/library/cx06ysxh(VS.80).aspx</font></u></a>&nbsp;（预定义的规则）</p>
<img src ="http://www.cppblog.com/hacrwang/aggbug/27294.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hacrwang/" target="_blank">hacrwang</a> 2007-06-30 20:19 <a href="http://www.cppblog.com/hacrwang/archive/2007/06/30/27294.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>