﻿<?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++博客-canmeng50401的专栏-文章分类-应用编程</title><link>http://www.cppblog.com/canmeng50401/category/16932.html</link><description /><language>zh-cn</language><lastBuildDate>Sun, 18 Sep 2011 23:10:47 GMT</lastBuildDate><pubDate>Sun, 18 Sep 2011 23:10:47 GMT</pubDate><ttl>60</ttl><item><title>IDE 不是程序员的唯一选择（六）（终）</title><link>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_6.html</link><dc:creator>纪灿萌</dc:creator><author>纪灿萌</author><pubDate>Sun, 18 Sep 2011 05:05:00 GMT</pubDate><guid>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_6.html</guid><wfw:comment>http://www.cppblog.com/canmeng50401/comments/156110.html</wfw:comment><comments>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_6.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/canmeng50401/comments/commentRss/156110.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/canmeng50401/services/trackbacks/156110.html</trackback:ping><description><![CDATA[转载自：<a href="http://blog.codingnow.com/2008/10/replacement_of_ide_6.html">http://blog.codingnow.com/2008/10/replacement_of_ide_6.html</a><br />
<div class="entry-content">
<div class="entry-body">
<p>对于这个系列，我已经意兴阑珊了。时间拖的太长也不好。从一开始我就没打算写一个某某工具（GNU Make）的入门教程。本来是想给那些微软 IDE 深度中毒者展现一些不同的东西，顺便打破 Make 等 CUI 工具的神秘感。工具是为人服务的，不应该是用来给人增添麻烦的。IDE 是这样，RAD 工具是这样，那些 CUI 工具也是如此。如果你能熟悉工具背后的使用哲学，工具就能给你便捷。不同的工作需要不同的工具去做，不要拿着锤子，就想把一切都变成钉子。</p>
<p>既然每一类工具都拥有特别多的用户，而且这个用户群还不都是脑残，去看看不同领域总是好的。对于开发环境来说是这样，选择编程语言来说也是如此，又或者说到开发方式等等。</p></div>
<div id="more" class="entry-more">
<p>今天不打算写长篇，简单点把这个系列完结。只谈一下前面欠下的一些问题。</p>
<p>对于大工程，在 VS 里，我通常是以虚拟文件夹和子工程的方式来管理的。不知道其他同学跟我是不是一个习惯。从 VC 6.0 以后，我几乎就没碰过 VS 了。不太清楚现在微软的 IDE 目前的发展趋势。我想可能有更好的组织方式吧。</p>
<p>但如果离开 VS ，我们用 GNU Make ，或是别的类似的工具（例如我用过的 Boost Jam ，或是前面有朋友推荐的 CMake 等）按惯例，通常按 OS 的文件目录结构来管理大的项目。即，一个子项目放在一个子目录中。对于一个大模块，即使它可能是一个子项目中不可分割的一部分，通常也以静态库的形式被分离出来。哪怕这个静态库只被一个地方引用。</p>
<p>把源代码拆分成适当的规模，并分类组织在不同的文件目录下，是一个好的习惯。</p>
<p>那么你在编写 Makefile 的时候，就可以为每个源代码的子目录编写一个 Makefile 。那么怎么让若干处于不同子目录下，甚至多层深度下的 Makefile 协同工作？Make 的惯例是利用 Shell 递归调用自己。</p>
<p>比如你的 src 目录下有两个子项目，foo1 和 foo2 。你在 src 根下的 Makefile 一般会这样写：</p><pre>all : 
    cd foo1 &amp;&amp; $(MAKE) all
    cd foo2 &amp;&amp; $(MAKE) all
clean:
    cd foo1 &amp;&amp; $(MAKE) clean
    cd foo2 &amp;&amp; $(MAKE) clean
</pre>
<p>$(MAKE) 是一个预定义的变量，里面保存的就是调用 Make 自己的 Shell 指令。</p>
<p>看起来比较繁琐，所以我的习惯是，把 foo1 foo2 这些子目录提取出来。</p><pre>DIRS= foo1 foo2

all : $(DIRS:=.all)
clean : $(DIRS:=.clean)

%.all :
    cd $* &amp;&amp; $(MAKE) all
%.clean:
    cd $* &amp;&amp; $(MAKE) clean
</pre>
<p>这个版本依然有许多重新的东西，也是可以去掉的，但那势必引出更多的&#8220;高级&#8221;用法，暂时就不展开了。其中用到的知识前面我们都介绍过了。除了 $* ，这个是表示目标中除掉 .ext 后缀的部分字符串。</p>
<hr />

<p>按前几篇的流程走下来，你会发现，在 build 工程的时候，往往在源代码目录留下许多中间文件。我们在前面的例子中都写上了 clean 这个目标，用来清除中间文件。但事实上，在 GNU Make 的手册里，并不建议我们如此的污染源代码目录。一般来说，我们会定义一个中间文件的输出目录。这需要少许的技巧，但是不难办到，这里就不举例了。</p>
<hr />

<p>因为早年使用 VS 的缘故，我喜欢同时维护至少两个版本的中间文件。一个 Debug 版，一个 Release 版，分放在不同的中间文件目录中，重新 build debug 版，不会影响到 Release 版的重构建。对于这个需求 Boost Jam 做的相当不错。甚至弄的更华丽，你可以轻易的拥有 "关闭 RTTI 设置的 Release 版&#8220; 、&#8221;打开 C++ 异常的 Debug 版&#8220; 等等。不过 boost jam 也为这华丽的功能付出了一点点小小的代价&#8230;&#8230; </p>
<p>我们说回 Make ，最简单的方法是，再编译不同的版本的时候，选用不同的变量取值，例如：不同的优化开关、不同的输出目录。GNU Make 支持类似 C 语言中 #if 的条件语句，但是不能完全解决这个问题。还需要动用的一个特性是，GNU Make 支持定义目标相关的变量：</p><pre>release : CFLAGS=/O2

debug : CFLAGS=/Zi
</pre>
<p>即这样的写法，它使得一个变量的取值在编译某个特定目标的时候才有效。具体怎么做达到你心目中的需求，就留给同学们自己思考了。</p>
<hr />

<p>记住，是工具就需要学习。多用才可以提高对工具的掌握程度。对于编写 Makefile 文件，不要抱着修改配置文件那样的心态，而要把它当成一门语言，一门可以提高你的工作效率的语言。通过计算机语言教会计算机做本该你亲手来做的操作，这是程序员之道。</p></div></div><img src ="http://www.cppblog.com/canmeng50401/aggbug/156110.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/canmeng50401/" target="_blank">纪灿萌</a> 2011-09-18 13:05 <a href="http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_6.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IDE 不是程序员的唯一选择（五）</title><link>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_5.html</link><dc:creator>纪灿萌</dc:creator><author>纪灿萌</author><pubDate>Sun, 18 Sep 2011 05:04:00 GMT</pubDate><guid>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_5.html</guid><wfw:comment>http://www.cppblog.com/canmeng50401/comments/156109.html</wfw:comment><comments>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_5.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/canmeng50401/comments/commentRss/156109.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/canmeng50401/services/trackbacks/156109.html</trackback:ping><description><![CDATA[转载自：<a href="http://blog.codingnow.com/2008/10/replacement_of_ide_5.html">http://blog.codingnow.com/2008/10/replacement_of_ide_5.html</a><br />
<div class="entry-content">
<div class="entry-body">
<p>国庆休完了，干了许多事情，几乎没闲着。接下来写这个系列，有点提不起兴致的感觉。</p>
<p>如果一直讲 GNU Make 的话，就有点离题了。本来我是想讲讲，离开 IDE ，程序员该如何处理问题的。Make 只是一个起点。写着写着，就已经写的足够的多，可又似乎什么都没讲出来。有心的同学应该已经找到 GNU Make 的中文手册自己去研究了，我想大家若结合自己做过的项目，会发现其中奥妙无穷。而比较乐意享受快餐文化的另一批同学，可能还在等我的下文。该怎么说？还是先引用 VIM 主页上介绍 vim 的一句话，</p>
<p>Vim isn't an editor designed to hold its users' hands. It is a tool, the use of which must be learned.</p>
<p>是的，Make 也是，更多的编程开发工具都是。既然是工具，就必须付出学习成本。如果你觉得使用某种工具不需要支付学习成本，那么你一定失去了一些东西。只不过，你未必意识的到而已。</p></div>
<div id="more" class="entry-more">
<p>为了利用计算机帮助我们达到一个目的，原本有许多方法。有普通人的方式，有程序员的方式。程序员的方式是教会计算机代我们去做；普通的人方式是利用计算机协助我们去做。在 IDE 里，有时我们也想办法用独特的方式去教计算机做一些事情。IMHO, C++ 以及其千奇百怪的特性挖掘，建立出丰富的模板库很大程度就是来源于此。比如 boost 里有个叫 Boost::Spirit 的库，可以让 C++ 程序员书写近似的 BNF 的式子，生成一个解析器去分析它。但是为什么不考虑直接用 yacc 呢？它天然的就是为了处理 bnf 而存在的。因为 IDE 并不是一个很好的粘合剂，用来粘合这些工具。所以，我们把粘合层下移到了 C++ 编译器的层面。不幸的是，一旦我们给一个单一工具附加了太多使命后，无论是 IDE 还是 C++ 编译器，它们都会迅速的臃肿，直到不能承担；或者，我们干脆改变问题，让问题去适应解决手段，而不是接受手段去适应问题。</p>
<p>Make 是一个好的起点，它展示了一个功能单一简洁的工具，能且只能完成一个工具粘合剂的使命。并且完全以计算机的方式去工作。工具和工具之间以命令行，返回码，标准输入输出管道的方式藕荷。和人交互的界面是简单可读的文本。层次分明，顾而可堪大任。</p>
<p>多说无益，既然已经没有多少兴致向下写。就把剩下的时间集中在几个前面提到过的问题的解决上。剩下的一切就靠同学们自己研究摸索了。离开 IDE ，你应该得到了一片更广阔的天空，一切适合计算机协作的工具（有命令行接口）都能拿来使用。你不用再局限于别人是否给你提供了源代码，是否有良好的 DLL 插件接口，而只用关心前人做的工具是否适合你现在的需要。你用自己的头脑去发现捷径，而不是 google 一个 step by step 的教程。</p>
<p>问题一，如何让编译流程自动得到 .h 头文件的依赖关系？</p>
<p>C 语言的源文件依赖头文件是一个语言层面的东西，跟 Make 工具无关。有些构建工具，比如我曾经使用过的 boost jam 可以编写代码扫描器来完成这些，但 GNU Make 是没有的。没有并不是坏事，一个优秀的工具的原则应该是：努力做好一件事，并只做这一件。其实，分析源代码找到依赖之头文件，并不是一件容易的事情，至少你需要实现 C 语言的宏的解析工作。这应该是 C/C++ 编译器的一部分。</p>
<p>可惜我在 VC 中没找到编译选项可以只完成头文件依赖关系的处理流程。所以，如果你想在 Windows 下使用机器自动生成依赖关系，还得装一个 gcc 。Mingw 的 gcc 是一个不错的选择。你完全可以用 gcc 去分析头文件的依赖关系，再用 vc 来编译代码，虽然有点奇怪，但有何不可呢？一切工具皆可为我所用。</p>
<p>gcc 在命令行加上参数 -MM ，即可生成 GNU Make 格式的依赖关系文本（包括直接和间接的关系）。这个文本输出在标准输出上。但是，其输入格式里，目标文件的后缀是 .o 而不是 VC 默认的 .obj 。不过没有关系，VC 编译 .c 生成 .o 而不是 .obj 就好了。</p>
<p>下面来看我们前面几篇提到的那个小项目，foo.c bar.c foo.h 最后编译生成 foobar.exe 。我写一个完整的改进版在下面：</p><pre>SRCS=foo.c bar.c
OBJS=$(SRCS:.c=.o)
MAKEDEPEND=gcc -MM
CFLAGS=/Zi 

all : foobar.exe

.depend:
    $(MAKEDEPEND) $(SRCS) &gt; $@

depend:
    -del .depend
    $(MAKE) .depend

clean :
    -del foobar.exe $(OBJS)

foobar.exe : $(OBJS)

%.exe :
    link /out:$@ $^

$(OBJS) : %.o : %.c
    cl /c /Fo$@ $(CFLAGS) $&lt;

include .depend
</pre>
<p>这里出现了许多前面没有提到过的陌生用法。下面一一讲解：</p><pre>SRCS=foo.c bar.c
OBJS=$(SRCS:.c=.o)
</pre>
<p>仅凭猜测，我们就能知道，OBJS 会被定义成 foo.o bar.o 。GNU Make 有着强大的字符串处理功能，非常适合做这类事情。像这种后缀匹配替换，用这个简单的语法就可以表达了。如果在 SRCS 里定义了多种不同后缀的字符串，比如有 .c .cpp .asm 等等，我们可以先筛选出 .c 的部分，即 CSRCS = $(filter %.c,$(SRCS)) 。这里不再详述。</p>
<p>需要稍加说明的是这里的一个赋值：OBJS=$(SRCS:.c=.o) 。OBJS 的值依赖了另一个变量 SRCS 。那么，OBJS 的求值过程是什么是否发生的呢？这个对于敏感的程序员应该是头脑中立刻显现的问题。</p>
<p>答案是，直到 OBJS 被真正用到，取值的时候，$(SRCS:.c=.o) 的求值过程才被触发。也就是说，你可以在下面修改 SRCS 的值（通过 = 或 += 等）而 OBJS 的值总是正确的。</p>
<p>btw, 如果你希望求值过程立刻进行，可以用 := 而不是 = 。具体请翻阅 GNU Make 的文档。</p>
<p>在 Makefile 的最后，include .depend 包含了一个暂时还不存在的 .depend 文件。当 Make 工作的时候，发现一个文件不存在，都会试图构建它，include 指令中出现的文件也不例外。所以，第一次运行，会触发 .depend 文件的构建。</p><pre>.depend:
    $(MAKEDEPEND) $(SRCS) &gt; $@
</pre>
<p>通过调用 gcc -MM ，gcc 会输出类似 foo.o : foo.c foo.h 这样的文本行，借助命令行管道操作，我们把输出定向到了 $@ ，即文件 .depend 中。</p>
<p>而第 2 次运行 Make ，由于 .depend 文件已经存在，依赖关系则不再构建。</p>
<p>如果我们修改了 .c 文件中引用的 .h 文件怎么办？简单的方法就是删除 .depend ，重新 Make 。当然，这里我们提供了一个叫做 depend 的伪目标来帮我们做这件事情。</p><pre>depend:
    -del .depend
    $(MAKE) .depend
</pre>
<p>这里出现了一个 $(MAKE) 没有定义的变量。这个变量是由 Make 自己定义的，它的值即为自己的位置，方便 Make 递归调用自己。在以后的篇章里，我们会继续发现 Make 自身递归调用的威力。</p>
<hr />

<p>还有问题吗？</p>
<p>自然是有的。</p>
<p>.depend 文件并不能自动随着项目源码的修改而变更。这显然不够自动化。只不过我们在改进这一点时，需要先问一下自己，你的项目真的需要这个吗？很多项目，并不常变更依赖关系。而作为开发人员自己，在不段的修改代码时，心里非常清楚什么时候应该重新 gmake depend 。而对于第一编译项目，且很有可能不再修改源代码重新编译的人来说，生成一次 .depend 文件足够了。</p>
<p>我们可以简单的让 .depend 文件每次重新生成，这或许比仔细检查每个文件的变更情况然后重新生成更为廉价。（为什么？怎么做？这两个问题同学们自己思考：）</p>
<p>简单的在 .depend : 后加一个依赖关系，比如 .depend : $(SRCS) 行不行？在这个例子里没有问题，但是如果更复杂的情况下，比如 .h 文件中又包含了新的 .h ，可能就不能正确的重构 .depend 文件了。</p>
<p>另一方面，.depend 包含了所有 .c 文件和 .h 文件的依赖关系，因为一个文件的变更而重新扫描所有源文件值得吗？</p>
<p>这个问题不好回答。如果项目太大太大，或许有性能问题。不过，为什么你要把这么大的项目所有源文件放在一个子项目中？要知道，使用 Make 构建子项目，比如说一个静态库，是非常廉价的。（这个问题以后再来探讨）</p>
<p>无论如何，没有不能解决的问题。我们自然可以去生成独立的依赖关系描述文件，然后把依赖关系的描述文件和构建过程本身建立依赖关系。这个做起来比较复杂，但是也很有趣。（提示：gcc 的新版本命令行支持 -MMD ，可以把 .o 换为 .d ，这个功能目的何在？）不过，云风提醒大家，切勿钻牛角尖。一切工具只是帮助我们更便捷的达到目标。我们要认清目标，再选择好工具和恰当的用法。</p>
<hr />

<p>今天就到这里，下一次我们继续讨论另外几个常见问题：如何管理和组织源文件树木更多的项目，怎么组织负责的源代码目录树。以及怎样像 VS 做的那样，为 Release 版和 Debug 版的目标文件分目录存放，并保存源代码目录的清洁。</p></div></div><img src ="http://www.cppblog.com/canmeng50401/aggbug/156109.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/canmeng50401/" target="_blank">纪灿萌</a> 2011-09-18 13:04 <a href="http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_5.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IDE 不是程序员的唯一选择（四）</title><link>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_4.html</link><dc:creator>纪灿萌</dc:creator><author>纪灿萌</author><pubDate>Sun, 18 Sep 2011 05:02:00 GMT</pubDate><guid>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_4.html</guid><wfw:comment>http://www.cppblog.com/canmeng50401/comments/156108.html</wfw:comment><comments>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_4.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/canmeng50401/comments/commentRss/156108.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/canmeng50401/services/trackbacks/156108.html</trackback:ping><description><![CDATA[转载自：<a href="http://blog.codingnow.com/2008/09/replacement_of_ide_4.html">http://blog.codingnow.com/2008/09/replacement_of_ide_4.html</a><br />
<div class="entry-content">
<div class="entry-body">
<p>前面我们介绍了一些 Make 的基本知识以及 Make 的工作原理。如果同学们有兴趣的话，翻阅手册已经可以开始做许多事情了。而 GNU Make 更是有许多扩展，灵活应用便能发挥出超乎想象的威力。为了达到这些，Make 必须要有更多的一些可编程的能力。在处理不同的事务的时候，以不同的方式工作。在处理相同的事务时，又不必让编写 Makefile 的人机械性的重复。</p>
<p>今天，我们来谈谈 GNU Make 中的变量。这里提到的都是以 GNU Make 为基础，因为 Make 的各种变种太多，每种版本间都可能有一些细微的差别，但其基本原理是相通的。</p></div>
<div id="more" class="entry-more">
<hr />

<p>昨天我们最终的 Makefile 版本是这样的：</p><pre>all : foobar.exe

clean :
    -del foobar.exe foo.obj bar.obj

foobar.exe : foo.obj bar.obj
    link /out:$@ $^

foo.obj : foo.c
    cl /c $&lt;

bar.obj : bar.c
    cl /c $&lt;
</pre>
<p>我们能看出，其中有许多重复的信息，让我们一点点的去掉它们。</p>
<p>首先，将 .c 文件编译成 .obj 文件的方法是一样的。在 GNU Make 里，我们可以为其写一个通用的规则：</p><pre>%.obj : %.c
    cl /c $&lt;
</pre>
<p>注意，这并非传统的 makefile 的写法（传统规则定义方法就不介绍了），而是 GNU Make 扩展，以后我们就不再为 GNU Make 做特别说明了。</p>
<p>它的含义是，所有以 .obj 结尾的目标，都依赖于相同模式，但以 .c 结尾的文件。并且这类文件用统一的构建方法：cl /c $&lt; 。$&lt; 我们在前面已经反复提到过，它表示以上的第一个依赖文件。这个符号看起来有些怪异比较难记忆。如果你用 BSD Make ，那么就写作 ${.IMPSRC} 可能会舒服点。其实习惯了后都不错。</p>
<p>% 可以看成一通配符，%.obj 是一个后缀匹配，用 . 做文件后缀名的区分仅仅是一个习惯。如果你愿意，自然也可以写 %obj 去匹配所有obj 结尾的文件。</p>
<p>Makefile 既可简化为：</p><pre>all : foobar.exe

clean :
    -del foobar.exe foo.obj bar.obj

foobar.exe : foo.obj bar.obj
    link /out:$@ $^

%.obj : %.c
    cl /c $&lt;
</pre>
<p>因为这里只生成了一个 exe 文件，要是有多个 exe 文件生成，那个 link 也应该可以提取出来。可以写成：</p><pre>all : foobar.exe

clean :
    -del foobar.exe foo.obj bar.obj

foobar.exe : foo.obj bar.obj

%.exe :
    link /out:$@ $^

%.obj : %.c
    cl /c 
</pre>
<p>这个版本里，foobar.exe 和 %.exe 被分开定义了，根据我们前面的介绍，知道这样是合法的。一个目标可以有多次的依赖关系定义，Make 只是顺着往它的依赖关系表里添加而已。</p>
<p>接下来，我们发现 foo.obj bar.obj 在两个地方出现。程序员很自然的会想到，让我们用一个变量记录下它们。对 Make 支持变量。定义变量很简单，使用 = 赋值即可。还可以用 += 追加。更多的用法可以去查参考手册。而使用一个标量，则用 $(变量名) 这样的形式。因为 $ 对于 Makefile 文件有特殊含义，所以，一旦你需要在命令行部分（Tab 开头的那些行）写上 $ 就用两个代替，写作 $$ 。</p><pre>COBJS=foo.obj bar.obj

all : foobar.exe

clean :
    -del foobar.exe $(COBJS)

foobar.exe : $(COBJS)

%.exe :
    link /out:$@ $^

%.obj : %.c
    cl /c 
</pre>
<p>现在看起来是这样了。我们的定义了一个变量 COBJS ，保存了跟 foobar.exe 有关的所有 obj 文件名。然后在下面用这个变量替换掉。这是个很基本的用法，看起来没有什么意义，但是现在我们考虑一个问题：如果我们使用 C 和 C++ 混合编程，或者再使用了汇编等别的语言。这样，项目里的 .obj 文件就不全是由 .c 文件生成的了。这样，%.obj : %.c 这条规则有有了问题。</p>
<p>因为如果你连续定义 </p><pre>%.obj : %.c
    cl /c 

%.obj : %.cpp
    cl /c 
</pre>
<p>这里 %.obj 文件有了两条生成方法（虽然它们相同），这是不允许的。注意：简单的定义 %.obj : %.c 和 %.obj : %.cpp 则是合法的，它只是个 %.obj 增加了依赖关系而已。但是导致错误的解决问题：我们生成 .obj 文件并不需要同时拥有同名的 .c 和 .cpp 。</p>
<p>怎么办？</p>
<p>让我们进一步改进一下：</p><pre>$(COBJS) : %.obj : %.c
    cl /c 
</pre>
<p>写成这样即可。这一句的变量展开是什么呢？</p><pre>foo.obj bar.obj : %.obj : %.c
    cl /c 
</pre>
<p>根据前面我们已经具备的知识，我们知道这个等价于：</p><pre>foo.obj : %.obj : %.c
    cl /c 

bar.obj : %.obj : %.c
    cl /c 
</pre>
<p>这是一个严格的模式匹配的过程：foo.obj : %.obj : %.c 表示了，用 %.obj 试着匹配 foo.obj ，如果成功 % 就等于 foo ，如果不成功，Make 会警告你。然后，给 foo.obj 添加了依赖文件 foo.c （用 foo 替换了 %.c 里的 % ）</p>
<p>注：对于现在版本的 GNU Make ，（含 % ）模式规则定义可以不考虑这种问题。如果一个目标有两条规则可以匹配上，会通过检查所依赖的目标是否存在来决定适用哪条规则。但是，以上的方案在间接目标构建中依然有用。</p>
<p>现在回头来看看昨天的一篇最后那个问题：为什么 foo.c 中包含了 foo.h 文件，但是不能在 Makefile 中写 foo.c : foo.h ？</p>
<p>这是因为 Make 在工作的时候，每个目标的构建的单独判断所依赖的文件是否存在或其时间的。如果，foo.h 被修改，的确会触发 foo.c 的构建。不过这里我们只描述了 foo.c 和 foo.h 的依赖关系，而没有写任何构建指令。缺少构建指令时，默认是成功的。但是 foo.c 的时间并没有修改。这样在接下来的 foo.obj 构建过程中，foo.obj 的时间新于 foo.c （foo.c 未被修改过），故而 foo.h 的修改不会触发 foo.obj 的构建。</p>
<p>btw, 随着 Makefile 的复杂度增加，调试会是一个问题，上次我们介绍了 gmake 的 -n 参数，以观察执行流程。其实还有一个更为强大的 -d 参数，有兴趣的同学可以自己试试。</p>
<p>如何解决这个问题呢？一个苯办法是，当 foo.h 修改后，同时也更新 foo.c 的时间。在 *inx 下，有一个 touch 指令可以做这件事情。我们写作：</p><pre>foo.c : foo.h
    touch $@
</pre>
<p>Windows 下没有 touch 指令，但是 copy 可以代劳：</p><pre>foo.c : foo.h
    copy $@ +
</pre>
<p>这样做有些坏味道，明明是 foo.h 的修改，凭什么要更新 foo.c 的文件时间呢？所以，一般的做法是，让 foo.obj 直接依赖于 foo.h 。这样，我们的 Makefile 就变成了这样。</p><pre>COBJS=foo.obj bar.obj

all : foobar.exe

clean :
    -del foobar.exe $(COBJS)

foobar.exe : $(COBJS)

foo.obj : foo.h

%.exe :
    link /out:$@ $^

$(COBJS) : %.obj : %.c
    cl /c 

</pre>
<p>如果我们需要随时开关调试状态怎么办？也就是说，希望更灵活的打开或关闭 /Zi （调试信息）/O2 (最大速度优化）这样的编译开关。通常会预留一个变量。</p><pre>$(COBJS) : %.obj : %.c
    cl $(CFLAGS) /c $&lt;
</pre>
<p>然后再最前面定义 CFLAGS =/Zi</p>
<p>我们还可以把最下面两段比较通用的规则放到一个独立文件 compile.mk 里，就好象 C 语言里的头文件那样。</p><pre>CFLAGS=/O2    #优化
#CFLAGS=/Zi    #调试

COBJS=foo.obj bar.obj

all : foobar.exe

clean :
    -del foobar.exe $(COBJS)

foobar.exe : $(COBJS)

foo.obj : foo.h

include compile.mk
</pre>
<p>注意前面两行的 CFLAGS 的定义，它放置了一些我们以后可能会调整的编译选项。这里 # 表示注释，相当于 C++ 里的 // </p>
<p>再注意最后的 include 语句，它完成的工作非常类似于 C 语言中的 #include ，即把一段文本插入当前的 Makefile 文件。只不过，Make 的 include 比 C 语言下的强的多。它可以在 include 后跟多个文件名，而文件名也可以是一个变量。甚至 include 的文件本身也可以是一个目标文件。如果 include 的文件不存在时，可以调用自身对应的目标将其生成出来。这些用法，我们会在以后的时间里细细展开。对于 C 语言头文件的依赖关系的自动生成，通常需要类似的技术来实现。</p>
<p>附上 compile.mk 文件</p><pre>%.exe :
    link /out:$@ $^

$(COBJS) : %.obj : %.c
    cl $(CFLAGS) /c $&lt;
</pre>
<hr />

<p>今天，我不想一直的围绕编译构建 C/C++ 工程谈下去。如前面所说，Make 可以帮助我们简化日常的许多工作。比如今天，我就碰到一个需求：要把一颗目录树下的所有图片文件转换成一种私有格式。</p>
<p>我们有自己的命令行转换工具，每次可以转换一个文件。（这种图片转换我想很多人都遇到过，对于通用图象格式间的互转，有一个非常好用的命令行工具，ImageMagick，google 即得）</p>
<p>我第一感觉想到的是使用命令行指令 for 。不知道的同学可以输入 for /? 查询。</p>
<p>for 可以帮我遍历目录树，并对每一个符合要求的文件做一段命令行指令。但是，这里有几个问题。我们需要转换的图片非常多，有上万个文件。全部转换一次非常长的时间。而转换工具有点小问题，中间如果出了故障，不方便继续。另一方面，我的台式机是双核的，很难利用两个核同时工作。</p>
<p>所以我写了一个 Makefile 来做这件事情。首先我用 for 指令生成了需要转换的文件名列表。</p><pre>filelist:
    echo # &gt; $@ &amp;&amp; for /R %%I in (*.jpg) do echo LIST+=%%~pI%%~nI.dat &gt;&gt; $@
</pre>
<p>这小段代码会生成一个叫 filelist 的文件，里面有所有的 jpg 文件名。但是把扩展名 jpg 换成了我们的目标扩展名 .dat 。下面是一段针对转换工具的调用：</p><pre>$(LIST) : %.dat : %.jpg
    convert $&lt;
</pre>
<p>完整的 Makefile 最终是这样的，由于是随手写的，有些地方并不完备，但它可以快速工作。</p><pre>include filelist

all: $(LIST)

filelist:
    echo # &gt; $@ &amp;&amp; for /R %%I in (*.jpg) do echo LIST+=%%~pI%%~nI.dat &gt;&gt; $@

$(LIST) : %.dat : %.jpg
    convert $&lt; -o $@
</pre>
<p>运行 gmake ，转换工作开始了。一开始会显示 makefile:1: filelist: No such file or directory ，表示 filelist 找不到，无法 include 。但是没关系，接下来 Make 找到了 filelist 的构造方法，立刻构造出这个文件列表来。all 这个缺省目标就是依赖这个文件列表上的所有文件的。所以，工作可以随时用 Ctrl-C 停下来，下次运行时，gmake 将根据文件时间继续上次的工作。</p>
<p>这个 makefile 比较通用，其实可以放在任何地方，比如我把它塞在了 c:\tmp 下。需要转换那个目录下的所有 jpg 时，只需要进入那个目录，运行 gmake -f c:\tmp\makefile 即可。没错，gmake 可以用一个 -f 参数指定 makefile 文件的位置。这样，我们便有了一个方便的批量文件转换工具了。</p>
<p>怎么利用双核？太容易了。 gmake -j2 即可。-j2 表示用两个进程同时工作，如果是四核的机器，则可以 -j4 。打开 -j2 开关后，立刻可以发现 CPU 处于满负荷工作状态。短短几行代码就把这件事情搞定了。非常开心。</p></div></div><img src ="http://www.cppblog.com/canmeng50401/aggbug/156108.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/canmeng50401/" target="_blank">纪灿萌</a> 2011-09-18 13:02 <a href="http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_4.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IDE 不是程序员的唯一选择（三）</title><link>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_3.html</link><dc:creator>纪灿萌</dc:creator><author>纪灿萌</author><pubDate>Sun, 18 Sep 2011 05:00:00 GMT</pubDate><guid>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_3.html</guid><wfw:comment>http://www.cppblog.com/canmeng50401/comments/156107.html</wfw:comment><comments>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_3.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/canmeng50401/comments/commentRss/156107.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/canmeng50401/services/trackbacks/156107.html</trackback:ping><description><![CDATA[转载自：<a href="http://blog.codingnow.com/2008/09/replacement_of_ide_3.html">http://blog.codingnow.com/2008/09/replacement_of_ide_3.html</a><br />
<div class="entry-content">
<div class="entry-body">
<p>有了前面的介绍，相信好学的同学们对 make 已经有了一定的了解。</p>
<p>记住，编写 Makefile 也是构建整个软件的一部分，其重要性并不亚于编写 .c 或 .h 文件。当你用 IDE 的时候，是由 IDE 来生成相当于 Makefile 的文件。但是这个生成的过程并不是完全自动的，它是由你的鼠标点击、拖拽（把 .c 文件加入项目）、和填写一些表单、以及勾选编译选项完成的。</p>
<p>如果你的整个项目中其它源文件都是从键盘输入来编写的，那么 Makefile 也由手工编写就理所当然了。</p>
<p>如果你在用 C++ ，在用 C++ 中的 STL ，那么是不是应该搞清楚 STL 到底做了什么，怎么做到的，这些问题呢？这是用 C/C++ 语言编程的程序员的一个基本态度。即使不去研究透彻，至少也应该了解一些大致的原理吧。那么对于 Make 来说也是这样。我们应弄清楚 Make 到底如何在工作，我们在 Makefile 里写的每一行代码是什么含义。为什么可以帮我们完成那些工作。这个连载选择 Make 来展开，也正是因为 Make 的工作原理非常简单，方便我们学习。</p>
<p>越是简单的东西，越可以在其上做出各种奇妙的东西。Make 也是这样。但一开始就给出别人做好的完善的库，简单的用一下，会让我们迷失其真谛。相信我，最终，编写 Makefile 可以非常简单，善用工具、不必写任何多余的东西，甚至比在 IDE 里拖入几个源文件更简洁。但一开始，还是从繁琐开始，这些繁琐都是帮助你去理解，等你理解了自然能找到方法简化这些繁琐的工作。</p>
<p>云风绝非使用 Make 的高手，从某种意义上来说，也是一个入门者。写这个系列的时候，也需要去查阅文档确认是否写错。平时工作的时候，Makefile 文件通常也需要多次调试，才能正确的完成工作。也正是如此，才能体会到：怎样去理解和学习，容易跨过最初的门槛。</p></div>
<div id="more" class="entry-more">
<hr />

<p>上一篇开篇，我们讲到了，如何用命令行分开编译 .c 文件，并把它们链接起来。这样做，可以使每次修改都可以让机器做最少的编译任务。对于小工程，这样做的意义不大。但是大工程，可能就能帮我们节约不少时间了。记住这一点，永远没有通用的最优方案。因为实施方案本身也是有成本的。我们只需要找到最直接最简单的方法就可以了。比如项目一开始，可以写一个最简单的 Makefile 文件，随着项目规模的扩大再逐步完善。</p>
<p>分开编译再链接这件事，人做起来都比较繁琐，把它教给机器去做，当然也会更繁琐。所以我没有在上一篇中详解。好学的同学应该会自己弄了，今天，我也来写写自己的方案。但在此之前，我们先梳理一下对 Make 的理解。</p>
<p>Make 是一个工作于非常简单的模式下的工具。它的内部有一张表，记录了目标文件之间的依赖关系。Makefile 就是用来描述这张依赖关系表的。对于依赖关系表的描述，用了一种非常简单的语法：</p>
<p>目标 : 依赖</p>
<p>这表示，"目标" 的构建依赖于 "依赖" 先构建出来。这里，"目标" 和 "依赖" 都是文件系统中的文件，而"依赖"本身也可以是一个"目标"。如果 "依赖" 的文件时间新于 "目标" 的文件时间，表示 "目标" 需要重新构建。如果 "目标" 文件不存在，也会触发这种构建过程。</p>
<p>一个目标可以有多个依赖，可以在 : 后以空格分开写上多个，比如：</p>
<p>目标 : 依赖1 依赖2 依赖3</p>
<p>这在我们前一篇中已经多次见过了。其实还有另一个规则，我们可以写：</p>
<p>目标 : 依赖1</p>
<p>目标 : 依赖2</p>
<p>这样分两行写，即，每次写 "目标 : 依赖" 都在依赖关系的依赖关系表中添加了一项。（关于同名的依赖添加的问题，以后我们在讨论）</p>
<p>举个例子：</p>
<p>a : b c</p>
<p>和</p><pre>a : b
a : c
</pre>
<p>其实是等价的。</p>
<p>每个目标的构建方法并不是由 Make 内置功能完成的，Make 只是简单调用写在 "目标" 定义的下一行的若干命令行脚本而已。而每一行命令行脚本必须以 Tab 键开头。注意，如果你把目标的依赖关系分成若干行实现，只可以有一个地方定义构建脚本。</p>
<p>举例：</p><pre>all : a
all : b

a :
    echo $@

b :
    echo $@
</pre>
<p>这样一个 Makefile 用 Make 运行后，会显示：</p><pre>echo a
a
echo b
b
</pre>
<p>为什么呢？因为 Make 会找 Makefile 中定义的第一个目标，做为它这次的终极目标。在这里是 all 。all 依赖于 a 和 b 两个目标。由于你的工作目录下没有 a 和 b 两个文件，所以触发了 a 以及 b 的构建。而 a 的构建指令是 echo $@ ，$@ 指代了当前目标 "a" ，结果就执行了 echo a 。同样，b 文件的不存在，导致了 b 的构建，执行了 echo b 。</p>
<p>需要强调的是，echo 并不是 Make 的内置功能。echo 是 Windows 命令行指令（在 *nix 系统上，称为 shell 指令）。Make 只管忠实的执行那些相关的以 Tab 开始行内描述的命令行指令。目标的构建成功也不以目标文件是否正确生成为依据。而是以命令执行的结果是否为 0 。记得学 C 语言的时候，老师教你，main 函数得到正确结果时，应该 return 0 吧。这个 main 的 return 0 就是返回给系统用的。Make 通过检查这个返回值来觉得命令行指令是否被正确的执行。如果收到非 0 的返回值，整个 Make 的过程会被中断。当然，echo 这样的指令，一般都会返回 0 的。</p>
<p>注意，一个目标是否被正确构建，只取决于构建它的命令行指令是否正确的返回 0 ，而不取决于文件是否被创建出来或是被更新到最新的时间。在一次构建中，每个目标最多只会被构建一次。这是由于 Make 只对依赖关系表做一次简单的拓扑排序，然后就开始工作了。</p>
<p>同样，我们还可以让多个目标依赖同样的东西：</p>
<p>a b : c d</p>
<p>就等价于</p><pre>a : c d
b : c d
</pre>
<p>现在我们可以看到，在 Makefile 里写 : 定义，其实就是在填写一张依赖关系表。每次的一个 : 都向表里追加一些项目。Make 工作的时候，先读完整个文件，把完整的依赖关系表建立好，再根据命令行指定的目标开始工作，如果在命令行不指定目标，默认就是 Makefile 里写的第一个目标了。</p>
<p>这样我们就好理解，除了 Makefile 里的第一个目标定义之外，所有的依赖关系描述都是无所谓书写的先后次序的。Make 只管向依赖关系表里添加表项，不会删除。原则上也不保证按依赖表中的次序先后来构建，如果你需要一个目标一定先于另一个目标构建，就需要显式的描述出依赖关系。</p>
<hr />

<p>让我们回到早先的例子：从 foo.c 和 bar.c 构建出 foobar.exe 。并且要求 foo.c 和 bar.c 里若只修改了一个文件，就只编译新修改的那一个。</p><pre>foobar.exe : foo.obj bar.obj
    link /out:$@ $^

foo.obj : foo.c
    cl /c $&lt;

bar.obj : bar.c
    cl /c $&lt;

</pre>
<p>先别急着编译，我们来看看如何调试 Makefile 文件。使用 gmake -n 看看，是不是显示了：</p><pre>cl /c foo.c
cl /c bar.c
link /out:foobar.exe foo.obj bar.obj
</pre>
<p>如果显示的是，</p>
<p>gmake: `foobar.exe' is up to date.</p>
<p>那么就先 del foo.obj bar.obj foobar.exe 然后再看。</p>
<p>-n 参数可以让我们观察到底会执行些什么命令行指令，而不真的去执行它们。这对调试复杂的 Makefile 非常有用。</p>
<p>如果没有问题，就可以 gmake 构建出 foobar.exe 了。</p>
<p>如果你烦透了每次做一次实验都要手工清理一下 obj 和 exe 文件，那么可以把 del 指令也写在 Makefile 中。在上面的 Makefile 最后加上两行：</p><pre>clean :
    -del foo.obj bar.obj foobar.exe

</pre>
<p>当然，你也可以偷懒写成 del *.obj *.exe</p>
<p>同学们会注意到，del 前有个减号。这个 - 是告诉 Make ，忽略掉后面这行指令的返回值，不要因为返回值非 0 而中断。什么时候 del 会返回非 0 值呢？自然是要删除的文件不存在啦。</p>
<p>现在你就可以通过 gmake clean 来清除目标文件了。或者用 gmake clean foobar.exe 来先清理再编译，这相当于我们在 IDE 中使用的 rebuild 指令。Make 的命令行可以跟多个目标，它会顺着构建这些目标。对于举一反三的同学，应该已经给 Makefile 加了一个 rebuild 目标了：</p>
<p>rebuild: clean foobar.exe</p>
<p>暂时先这么写着，以后我们会介绍更好的，更偷懒的写法。</p>
<hr />

<p>最后给出一个云风当年初学 Make 时，犯过的一个错误。属于对 Make 理解不当造成的。</p>
<p>假设 foo.c 里 include 了一个 foo.h 文件，依赖关系应该怎么写？</p>
<p>在 Makefile 的最后，加上一行</p>
<p>foo.c : foo.h</p>
<p>这样行吗？</p>
<p>你可以试试，修改一下 foo.h ，然后 gmake 一下,看看有没有重新编译 foo.o 。为什么不能工作呢？留给同学们思考了。</p></div></div><img src ="http://www.cppblog.com/canmeng50401/aggbug/156107.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/canmeng50401/" target="_blank">纪灿萌</a> 2011-09-18 13:00 <a href="http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_3.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IDE 不是程序员的唯一选择（二）</title><link>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_2.html</link><dc:creator>纪灿萌</dc:creator><author>纪灿萌</author><pubDate>Sun, 18 Sep 2011 04:59:00 GMT</pubDate><guid>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_2.html</guid><wfw:comment>http://www.cppblog.com/canmeng50401/comments/156106.html</wfw:comment><comments>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_2.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/canmeng50401/comments/commentRss/156106.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/canmeng50401/services/trackbacks/156106.html</trackback:ping><description><![CDATA[转载自：<a href="http://blog.codingnow.com/2008/09/replacement_of_ide_2.html">http://blog.codingnow.com/2008/09/replacement_of_ide_2.html</a><br />
<div class="entry-content">
<div class="entry-body">
<p><a href="http://blog.codingnow.com/2008/09/replacement_of_ide_1.html"><font color="#36414d">话接上回</font></a>，话说我们已经大致了解了 C 编译器的工作流程，知道了 IDE 在背后如何在驱动编译器生成代码。对于传统 IDE ，就是集成了编辑器、项目管理、编译器，和调试器等几个大件的一个庞然大物。其中 IDE 企图节省人力的最大的部分就是将源代码组织起来，自动生成其间的关系，调用编译器构建项目。</p>
<p>（此处删去几百字关于 IDE 优劣的讨论。因为我觉得这个话题会陷于无谓的争论，还是直入主题比较好。承接上篇的宗旨，本文只写给有兴趣学习相关知识却不知该如何入门的朋友。说服程序员放弃 IDE 不是本文的初衷。）</p>
<p>对于不太大的项目，比如学校里日常做作业。写一个 make.bat 文件管理你的 C 代码已经足够了。如果你用一些 windows style 的编辑器，比如流行的 editplus 之流，都可以配置相应的所谓 user tool，一键调用 .bat 构建出最终的程序。也可以设置捕获编译器的输出，方便的双击错误信息定位到源代码编译错误的地方。</p>
<p>如果想摆脱鼠标（纯键盘操作对于基于命令行的工作方式来说，非常的有效率。毕竟写程序最终也得靠键盘的。），可以考虑使用 vim 。不要惧怕学习新事物。任何被公认优秀的工具，都有学习的价值。vim 属于有一定学习门槛，但一旦掌握（一般程序员可能需要一周左右的时间熟悉），威力无穷的那种。我的另一个同事强烈推荐 emacs ，我没怎么用过。如果你打算写 10 年以上的程序，花上几天时间学习一个无数程序员公认好用的工具，这项投资我个人认为是非常值得的。</p></div>
<div id="more" class="entry-more">
<hr />

<p>使用 bat 的方式来构建项目有一个问题，那就是当项目慢慢增大时，效率受到影响。因为每次 build 一次，都要重新编译链接所有的源代码。而 C/C++ 从设计之初就考虑到节省编译时间，可以每个源文件独立编译的，然后最后在把分别编译出来的目标文件链接在一起。（关于编译和链接的概念，不明白的同学请自己 google ）下一步，我们可以每次只编译刚刚修改过的源文件，和那些没有修改过的源文件以前编译出来的目标链接在一起就够了。</p>
<p>早期的 C 编译器，但是将编译和链接过程分开由两个程序完成的（现在其实也是）。我们今天看到的类似微软的 cl 这种程序只是给独立的连接器加了个壳而已（gcc 也是这样）。下面我们来看怎样分开编译多个源文件，并链接它们。</p>
<p>如果只想编译一个 .c 文件，只需要在给 cl 加上参数 /c （ gcc 是加 -c ）。那么编译上一篇中提到的例子 foo.c ，即用命令行指令：</p>
<p>cl /c foo.c</p>
<p>我们可以看到，当前目录下生成了目标文件 foo.obj</p>
<p>我们可以用同样的方法生成 bar.obj 。即 cl /c bar.c</p>
<p>那么如何把 foo.obj 和 bar.obj 链接起来？还是用 cl 即可。</p>
<p>cl /Fefoobar foo.obj bar.obj</p>
<p>cl 这个壳会正确的识别输入文件的类型，做出对应的链接这个行为。我们也可以直接调用微软的链接器：Link 。</p>
<p>link /out:foobar.exe foo.obj bar.obj </p>
<p>注意，这里输出文件必须写全 .exe 后缀。</p>
<p>罗罗嗦嗦写了一大堆，主要是想加深以前不太明白这些的同学们的印象。如果是玩 IDE 的老鸟，其实在 IDE 的各种设置中，差不多也都看过这些了。关于编译器的命令行参数不记得不要紧。一则可以用 /? 看帮助；二还可以去 VS 的 IDE 中对应的设置菜单里看看，通常一个 IDE 菜单选项的修改，都对应了最终命令行参数的差别。生成 exe 还是生成 dll ；使用控制台模式，还是标准 Win32 程序；预定义些什么宏，打开调试信息或是开启编译优化，无所不在。</p>
<p>记住这些参数的写法并不容易，也没有必要。因为相对于编写代码，敲打这些编译开关只占很少的工作。但要记住，如何调用编译器去构建工程，这个步骤本身其实也是项目构建的一部分。亲手写过，也就多了一分了解。</p>
<hr />

<p>编程之道在于，让机器做机器的事，人专注于人的思考。如果真的靠手键入每条编译指令，无疑让程序员去做了机器之事。把编译指令写入批处理（脚本）文件，仅仅只是节省了每次编译的重复劳动，并没有从根本上解脱。</p>
<p>在从 C 的源代码构建最终的执行文件这个流程中，哪些是人的创造，哪些是机器应行之事？显而易见，程序员应该做的是：</p>
<ol><li>
<p>教会机器，如何把一个 .c 文件编译成 .obj 文件。</p></li><li>
<p>教会机器，如何把若干个 .obj 文件链接成 .exe 文件。</p></li><li>
<p>告诉机器，你的项目由那些 .c 文件构成，最终你想生成一个叫什么名字的 .exe 文件。</p></li></ol>
<p>其中前两步，对于大多数类似的项目来说是共同的，所以我们只用也只应该教机器一次。而第三步，提供一个文件列表和一个目标文件名给机器即可。</p>
<p>实现以上目标，显然我们需要额外的工具。IDE 的项目管理及构建模块是一个选择，但不是唯一的选择。IDE 对最为常见的构建需求做的相当不错。也就是说，IDE 完美的教会了机器前两个步骤，我们无须干预。但这件事情意义有多大呢？既然我们现在已经知道整件事情的流程手工怎么进行，完成它就有了无数的选择。教会机器做这前两件事情，只是一项一次性劳动。我们可以通过教授机器做这件事的过程，学会更多东西。而后举一反三去干更为复杂的工作。下面，云风将介绍一种叫做 MAKE 的工具。它是我们完成这个目的的第一个台阶。</p>
<hr />

<p>为什么是 Make ？</p>
<p>我想说， make 只是诸多选择中的一个。它绝对不是最好的，但它是最容易理解的。make 以一种极其简单的规则运作，让程序员可以轻易看透它的实质。而简单的工具组合起来往往可以发挥出极大的能量。因为简单，所以 make 也很容易学习。你只需要掌握它很少的一部分功能，它就帮助你完成各种各样的工作。绝不仅仅是编译程序这么简单。你可以用它收发邮件、下载文件、制作安装包、运行单元测试，等等你能想的到的在你的机器上用命令行能够完成的所有事情。当然这些事情不用 make 也有别的方式去做，只是 make 用起来更方便。Make 会按你告诉它的事情的依赖关系，决定了做事情的前后次序，然后批量完成交给它的任务。如此而已。如果你用过 FreeBSD ，一定会爱上它的 ports 。需要什么软件，进入对应目录，make install 。下载、配置、编译、安装，一气呵成。这就是 make 的威力。</p>
<p>学会了 Make 后，你再接触别的项目构建工具，就不会有太多障碍。Make 做起来比较困难的事，也可以再高一个层次的工具来完成，比如 Automake 。我们要做的是，认识问题是什么，选择合适的工具，用程序员的方式解决它。</p>
<p>Make 有许多分支版本，细节使用起来各有差异。VS 里带了一个叫做 NMAKE 的小工具，是 Make 的一个旁支。我本来想从这个讲起，无奈用的不多，还是换成日常用的比较多的 GNU Make 吧。gmake 的内建指令也更为丰富，稍微熟悉一下，得心应手。更难得的是，在 Windows 下获得 gmake 非常方便，使用 Mingw 版的 gmake 即可，在 google 搜索 &#8220;mingw gnu make&#8221; 即可。其实不需要安装，它是一个绿色软件，一个独立的一百多K 的 gmake.exe 小程序就可以直接运行。</p>
<p>下面，云风假设你已经正确安装了 Mingw 版的 GNU Make （我的 Windows 系统上安装的版本是 3.81 ）。在命令行下，无论在什么目录下都可以直接输入 gmake 运行。（默认安装的 Gnu make 的执行文件名可能不叫这个名字，但你可以自己改个顺手的名字）</p>
<p>让我们开始吧。</p>
<hr />

<p>还是在一个你可以随意做实验的目录，顶好是你已经创建了 foo.c bar.c 的那个目录下，新建一个叫做 Makefile 的文本文件。编译它，写上：</p><pre>all :
    echo Hello World
</pre>
<p>这是你的第一个 Makefile ，输入请小心。第一行的 all: 应该顶格写，而第二行 echo 之前，必须有一个 Tab ，而不能用空格替代。即：第二行必须是 Tab 打头。</p>
<p>Tab 不是可以忽略的空白字符，并且是 Make 工作的关键。这个设定早就为许多人诟病，没有正确的输入 Tab ，也是许多 Make 初学者常见的错误。骂归骂，只能说这是一个历史原因造成的。好在一旦你习惯它，同样会觉得编写 Makefile 其实是非常顺手的。而且现在很多编辑器都可以让 Tab 明显的显示出来，而不会和空格混淆。</p>
<p>现在，运行 gmake ，你会看到它调用了命令行指令 echo Hello World ，回显了这行字符串。庆祝一下，你成功运行了自己编写的第一个 Makefile 。</p>
<p>Make 的一切设计都是为了简单、快捷。你把要做的事情写在一个文件中，运行 gmake 去跑这些任务。按通常的设计，gmake 应该跟一个任务文件的文件名，指定跑哪个文件。但是为了简洁，gmake 默认去找当前目录下的名为 Makefile 的文件了。至于你想把这些任务放在别的文件中，可以通过 gmake 的参数控制。有兴趣的同学可以按 Gnu 软件的习惯，通过 gmake --help 查询。</p>
<p>光能显示一行 Hello World 显然离题万里。接下来我们看看让 gmake 帮我们编译程序。不要删除前面的 Makefile 文件，再后面追加几行：</p><pre>foobar.exe :
    cl /Fefoobar foo.c bar.c
</pre>
<p>记住第二行开头的 Tab 不要敲漏了。</p>
<p>现在你的 Makefile 文件看起来应该是这样：</p><pre>all :
    echo Hello World

foobar.exe :
    cl /Fefoobar foo.c bar.c
</pre>
<p>我们再运行一下 gmake foobar.exe 看看：gmake 调用了你写好的 cl 指令，编译出了 foobar.exe 这个文件。</p>
<p>OK，大家应该看出点什么。Makefile 比传统的 bat 批处理文件多了点功能。它可以把多个任务放在一个文件里，而不需要我们写多个文件。为了区分任务，我们在命令行指定要做什么。</p>
<p>那些顶行写的文本，如果由一个单词加一个冒号开始。这个单词就被称为一个目标。gmake 目标，就可以做对应的任务了。而 gmake 会去做什么呢？自然由目标定义的下面几行决定。所有以 Tab 开头的文本行，定义了完成这个目标应该执行的命令行指令。gmake 会运行目标定义之下一直到下一个目标定义之间的所有指令。（注：Make 并不会保证这些指令的执行次序，虽然原则上是按你书写次序执行。这可以让 Make 使用多个 CPU 加快运行成为可能）</p>
<p>当我们在命令行输出 gmake all ，它就 echo Hello World ；而输入 gmake foobar.exe 它就调用 cl 去生成 foobar.exe 。如果没有直接输入 gmake 而不跟任何目标参数。它会自动寻找文件里的第一个目标。（而 Makefile 编写的惯例，我们通常把第一个目标起名字为 all ，这仅仅是一个惯例而已）</p>
<p>多试几次看看。</p>
<p>第 2 次运行 gmake foobar.exe 你会发现，gmake 报告：</p>
<p>gmake: `foobar.exe' is up to date.</p>
<p>而拒绝再次编译。</p>
<p>没错，这就是 Make 为数不多的原则之一：一旦目标已经存在，就直接跳过任务。</p>
<p>所有的目标，都被 Make 认为是一个文件，它的规则就是，如果目标文件存在，就认为事情已经做完。除非&#8230;&#8230;</p>
<p>让我们修改一下 Makefile 把 foobar.exe : 这行改成</p><pre>foobar.exe : foo.c bar.c
</pre>
<p>然后修改一下 foo.c 存盘，再运行 gmake foobar.exe 试试？又重新编译了对吧。</p>
<p>这就是 Make 的第二条原则：目标 : 后面以空格写上它所依赖的其它目标。如果所依赖目标存在，且比目标本身的时间新，就重新构键一次目标。</p>
<p>由于我们修改了 foo.c 导致了 foo.c 这个文件（对 Make 来说是一个目标，只不过没有构建这个目标的方法而已，只能靠用户自己编辑生成）比 foobar.exe 更新。foobar.exe 依赖 foo.c ，所以触发了 foobar.exe 的构建方法。</p>
<p>同样，我们可以让 all 依赖与 foobar.exe 。这样 gmake all （或省略 all 不写，因为 all 是第一个目标）时，由于不存在 all 这个文件，而触发 foobar.exe 的构建流程。</p>
<hr />

<p>接下来，我们回头来看看自己编写的这个 Makefile 文件。现在大约是这个样子：</p><pre>all : foobar.exe
    echo Hello World

foobar.exe : foo.c bar.c
    cl /Fefoobar foo.c bar.c
</pre>
<p>第 2 个目标 foobar.exe 的编写非常的累赘。程序员的直觉告诉我们，信息的重复不是一个好味道。那么让我们来修改一下。</p><pre>foobar.exe : foo.c bar.c
    cl /Fe$@ $^
</pre>
<p>这样是不是味道好点了？$@ 和 $^ 都是 Make 的内设变量，也可以看成是一种类似 C 语言中宏的东西。$@ 在指令被运行时，被宏替换为目标，而 $^ 被宏替换为所有的依赖目标，即冒号后面的那一长串东西。同样常用的还是 $&lt; ，可以替换所依赖的第一个目标。</p>
<hr />

<p>今天已经写的足够多了。没接触过 Make 工具的同学应该能初窥门径了。没错，从现在已经介绍的知识来看，Make 并没有为我们节省太多体力，甚至还多敲了许多字符，今天快结束时，居然还要多记几个诸如 $@ 这样古怪的符号。而做到的事情离我们的目标还很远。</p>
<p>没关系，云风会带着你渐入佳境的，那么，且听下回分解。</p></div></div><img src ="http://www.cppblog.com/canmeng50401/aggbug/156106.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/canmeng50401/" target="_blank">纪灿萌</a> 2011-09-18 12:59 <a href="http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_2.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IDE 不是程序员的唯一选择（一）</title><link>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_1.html</link><dc:creator>纪灿萌</dc:creator><author>纪灿萌</author><pubDate>Sun, 18 Sep 2011 03:48:00 GMT</pubDate><guid>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_1.html</guid><wfw:comment>http://www.cppblog.com/canmeng50401/comments/156101.html</wfw:comment><comments>http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_1.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/canmeng50401/comments/commentRss/156101.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/canmeng50401/services/trackbacks/156101.html</trackback:ping><description><![CDATA[<div class="entry-content">
<div class="entry-body">
<p>转载自：<a href="http://blog.codingnow.com/2008/09/replacement_of_ide_1.html">http://blog.codingnow.com/2008/09/replacement_of_ide_1.html</a><br />我心目中，这篇文章的目标读者应该是在 Windows 下完全使用 Visual Studio 或 Borland C++ Builder （现在还有人在用么？）等系列 IDE 开发软件的 C/C++ 程序员。</p>
<p>我并不打算从 GNU Make 这种工具的使用写起，因为如果以上提到的这类同学如果都开始看 gmake 的文档（现在翻译工作已经有人做了），应当已经脱离了纯粹 IDE 开发的人群。本文只是一篇非常初步的入门文章，如果你已经使用过类似 gnu make 的工具构建自己的项目，那么完全不必看下去了。</p>
<p>不可否认，IDE 对于软件开发领域，是一项伟大的发明。它极大的降低了软件开发的门槛。但是另一方面，IDE 也限制了程序员们创造软件的手段。这些限制还包括了平台限制，工具选择，甚至新的编译技术，编程语言的选择。所以 IDE 绝对不是程序员的唯一选择，如果你现在作为一个程序员，完全不能离开 IDE 工作。那么，是时候接触一些新东西了。</p>
<p>如果你大约知道一点相关的知识，但是对用 make 工具去构建项目充满了鄙视和厌恶，云风不期望通过这篇文章改变你的想法。因为我不想花太多笔墨来介绍其好处。我个人认为，那些好处，一旦你认真的采用这种开发方式，是显而易见的。</p></div>
<div id="more" class="entry-more">
<p>阅读本文，云风假设你至少已经了解了下面这些知识：</p>
<p>会使用 Visual Studio 的某一个版本创建一个由 C/C++ 程序构建起来的工程，并正确编译运行它。</p>
<p>知道 Windows 里有一个叫做控制台（或终端）的程序，通常用 Win-R 然后输入 cmd 启动它。</p>
<p>知道最基本的 dir cd mkdir del copy 等 Windows 命令行指令，并了解 Windows 文件系统的基本结构。</p>
<p>基本了解 "环境变量" 这个概念，知道 PATH 这种常见环境变量的用途。</p>
<p>如果你对上述概念不甚了解，请运用你使用 google 的技能把它们弄清楚。然后，我们可以开始了。</p>
<hr />

<p>读到这里还没有离开的同学，机器上应该装有一份 Visual Studio 。我的机器上就装了两个版本，但是差不多三年没怎么用它们做项目了。一份是 Visual Studio 6.0 ，早几年订购 MSDN 宇宙版送的。另一份是从微软网站免费下载的 Visual Studio 2005 Express Edition 。</p>
<p>我自己做项目现在是用 gcc ，并且向同学们推荐这款编译器。主要原因当然不是其免费，如上所述，我们现在也可以从微软免费获得编译器了。使用 gcc 最大的好处就是，一旦你打算切换到别的平台开发软件，可以不用更改你的使用习惯。就算你不离开 Windows ，也需要它来开发你的 psp ，nds ，手机，pda 等等。另外 gcc 一直在更新，它给你带来的好处是更强大的编译功能。如果你有一天像我一样放弃了 IDE ，Visual Studio 不断升级的 IDE 界面以及更华丽的工程管理方案对你也会毫无意义。</p>
<p>不过现在，我们暂时不要切换到 gcc 下。那样变化太大，反而难以接受。常年使用 vs 培养出来的习惯是很顽固的。等你学会了 make 这类工具，就会发现，其实切换编译器是再容易不过的事情了，到时候爱用啥用啥。</p>
<hr />

<p>现在进入 cmd 控制台模式。输入 cl 回车。如果你的机器上安装了 vs 2005 ，你应该可以看到</p>
<p>Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved.</p>
<p>usage: cl [ option... ] filename... [ /link linkoption... ]</p>
<p>如果是 vc 6 那么也大同小异，</p>
<p>Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86 Copyright (C) Microsoft Corp 1984-1998. All rights reserved.</p>
<p>usage: cl [ option... ] filename... [ /link linkoption... ]</p>
<p>如果没有出来，可能是环境变量没有配置好。进入 vs 的安装目录（C:\Program Files\Microsoft Visual Studio 8\VC 或 C:\Program Files\Microsoft Visual Studio\VC98\Bin），运行 vcvarsall.bat (vs2005) 或 VCVARS32.BAT (vc6) 即可。实在搞不定就重新安装 vs ，默认安装选项会帮你设置好所有的环境变量。</p>
<p>好了，如果你成功运行了 cl 这个程序，下面的一切都会很顺利。cl 是微软出品的 C/C++ 编译器，就是把 .c 或 .cpp 的源文件编译生成为 PE 文件的工具。常年使用 IDE 的同学也应该注意到它的存在，就是在你使用 vs ide 时，按下 build 的按钮，下面 output 窗口里也会蹦出正在运行 cl 的信息。</p>
<hr />

<p>现在创建并进入一个工作目录，比如 C:\project\foo ，我们将在这个目录下进行今天的演示。</p>
<p>用你喜欢的文本编辑器在这个目录下编辑一个文件名为 foo.c C 程序。写点 hello world 之内的东西即可。</p>
<p>接下来使用 cl foo.c cl 将为你生成两个文件，一个是 foo.obj 一个是 foo.exe 。我们应该明白这些是什么了。</p>
<p>现在可以试着运行 foo.exe 了，看看是不是你预期的结果？</p>
<p>好吧，对于这种一个源文件就可以搞定的简单程序，我个人认为，无论从任何角度讲，直接使用 cl 都比用 ide 要来的方便。至少我不用构建一个新的工程项目不是？有时候需要写一些实验性的代码，可以把一堆这样的小程序放在一个目录下，只要它们的文件名各不相同就可以了。完全不必在硬盘上留下大堆复杂的目录结构。</p>
<p>但这离构建一个项目还远远不够。通常一个 C/C++ 项目都是由很多个 .c .cpp 源文件，以及若干 .h 构成的。</p>
<p>比如，我们再添加一个 bar.c ，希望把 foo.c 和 bar.c 编译链接到一起。</p>
<p>无论是 cl 还是 gcc ，都支持直接在一个命令行写上多个源文件，然后依次编译并链接成最终的 PE 文件。所以最简单的做法是直接</p>
<p>cl foo.c bar.c</p>
<p>我们会看到， cl 生成了三个文件， foo.obj bar.obj 以及 foo.exe</p>
<p>cl 将 foo.obj 与 bar.obj 链接成了 foo.exe ，这个文件名默认是以第一个输入源文件为参考的。</p>
<p>如果我们想换个最终目标文件名怎么办？查一下 cl 的帮助，输入 cl /? 看看。</p>
<p>/? 是微软风格的命令行求助选项，几乎所有的微软编译工具都支持。如果是用 gcc 则是 gcc --help 。</p>
<p>我们在帮助信息里可以找到，能够用 /Fe 来指定最终生成的 PE 文件名。</p>
<p>现在可以用 cl /Fefoobar foo.c bar.c 试试看了，主要 /Fe 和 foobar 间不要留空格。</p>
<p>现在则生成的是 foobar.exe 而不是 foo.exe 了。</p>
<hr />

<p>调试怎么办？</p>
<p>我猜用惯 IDE 的同学们现在最想问的就是这个了。如果我再来推荐诸如写 log 这类 &#8220;原始&#8220; 调试方法，怕是要被人嗤之以鼻了。vs 的用户们肯定习惯了单步跟踪、设置断点、监视变量，等等这种 &#8221;高科技&#8220; 的调试手段。其实我也喜欢，方便的工具为何不用呢？</p>
<p>调试器的选择并不多，尤其在 Windows 下。但也不是别无选择的。使用 cl 自然就要使用 vs 自家的调试器啦。正如你使用 gcc 就必然选择 gdb 一样。（btw, gdb 习惯后其实并不难用，何况还有 insight ddd 这样的图形界面的选择，只不过他们的表现不如在非 windows 平台上那么好罢了。）</p>
<p>要使用调试器，必须先生成调试信息。对于 cl ，这个编译开关是 /Zi 不太好记，但是用的多了就熟了，一旦忘记了，请用 cl /? 查询。如果以后换成 gcc 那么就是 -g 了。</p>
<p>试试</p>
<p>cl /Zi foo.c</p>
<p>除了原来生成的 obj 和 exe 文件外，cl 还生成了 pdb 文件，这里面就存放了调试信息。当然，如果你用 gcc 的话，是不会有额外的文件的，调试信息就放在 exe 文件内部。</p>
<p>现在开启熟悉的 ide 来调试这个程序，当然这次，我们只是把 ide 当成调试器在用。如果不想使用 VS 的 IDE ，也可以使用微软免费提供调试器 WinDBG ，不过使用稍微麻烦一点，需要自己设定源码以及调试符号文件的路径，这里就不介绍了。</p>
<p>如果是在使用 vs2005 express 版, 请输入 vcexpress foo.exe ；如果是 vc6 的话，输入 msdev foo.exe 。效果都是一样的，都是打开 vs 的 ide 并加载 foo.exe 。</p>
<p>如果你讨厌命令行操作（嘿，同学。你都打算学习怎样不使用 ide 开发项目了，怎么能讨厌命令行环境呢？）, 可以先打开 vs 的 ide ，然后从菜单里选择 open ，打开 foo.exe 也可以。</p>
<p>接下来，按一下 F11 （vs ide 默认的 step into 的热键）。看到了什么？foo.c 被打开了，程序运行指示光标停在了 main 函数的第一行。:)</p>
<hr />

<p>接下来，云风将传授一门关于调试的独门秘籍。</p>
<p>知道调试器是如何实现断点这个功能的吗？其实它偷偷的在你的程序要设置断点的位置放置了一条调试中断指令。在 x86 32 位系统上，这是一条单字节指令，汇编代码是 int 3 。cpu 运行程序的时候，碰到 int 3 就会把控制权交给调试器，当你在调试器中选择继续运行的时候，调试器再将被替换的程序机器指令换回去，让程序继续运行。</p>
<p>知道了这个细节，我们就可以自己提前设置调试断点了。我称它为硬断点。只要程序运行到，就一定会停下来。如果你想在运行期屏蔽硬断点，需要在源码上做一些工作了。</p>
<p>现在在你刚才的 foo.c 的程序入口处加一行 __asm int 3 ( 如果你在用 gcc 可以加 asm ("int $3"); ) 重新用 cl /Zi foo.c 编译一次。然后在命令行直接运行 foo.exe 。</p>
<p>马上，你将会看到一个熟悉的关于程序崩溃的对话框。没关系，它是由你插入的硬断点 (int 3) 造成的。如果你正确安装了 vs ，vs 应该已经把自己设置成系统默认的调试器了。点对话框上的按钮，便将启动 vs 的 ide ，我们会发现程序正好停在了 int 3 那行汇编的地方。现在你可以尽情的单步跟踪了。</p>
<hr />

<p>写了这么多，似乎还没进入正题。我们一直在玩一些玩具代码和迷你工程。貌似离取代 ide 去工作还很远。只是我今天写累了，还是且听下回分解吧。今天这一篇，让一些完全没接触过命令行编译程序的同学们加深一下，一组源代码是如何生成最终的执行文件，这个过程的理解。达到这个目的就足够了。本质上，IDE 也在做这些事情，作为一个 C/C++ 程序员，怎能对一个天天在用的系统怎样在工作一点都不了解呢？</p>
<p>ps. 希望看到这里的同学不要为在命令行下输入了太多的指令而心烦意乱。或者担心那些命令行参数用完就忘。你知道 windows 下有个叫做批处理的好用的工具么？就是那些后缀为 .bat 的文件。可以把经常输入的指令放在里面，简化日常的命令行操作。如果不知道，那么还请 google 之。</p>
<p>虽然 windows 下的批处理比 *nix 的 shell 脚本弱了上万倍，但毕竟还是可以提高我们的生产力的。在没有介绍 make 工具前，同学们可以先用 bat 文件顶一下。比如将你的工程的编译指令一次性写到一个 .bat 文件里，就不需要每次重新编译都敲上长长的一串编译指令了。</p>
<p>事实上，云风最早从 ide 里出来，就是用批处理来管理自己的工程的。在下一回，本系列将隆重推出更加好用的工具来取代它。</p></div></div><img src ="http://www.cppblog.com/canmeng50401/aggbug/156101.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/canmeng50401/" target="_blank">纪灿萌</a> 2011-09-18 11:48 <a href="http://www.cppblog.com/canmeng50401/articles/replacement_of_ide_1.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用 event object 来进行线程间的同步</title><link>http://www.cppblog.com/canmeng50401/articles/event_object.html</link><dc:creator>纪灿萌</dc:creator><author>纪灿萌</author><pubDate>Sat, 21 May 2011 14:45:00 GMT</pubDate><guid>http://www.cppblog.com/canmeng50401/articles/event_object.html</guid><wfw:comment>http://www.cppblog.com/canmeng50401/comments/146885.html</wfw:comment><comments>http://www.cppblog.com/canmeng50401/articles/event_object.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/canmeng50401/comments/commentRss/146885.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/canmeng50401/services/trackbacks/146885.html</trackback:ping><description><![CDATA[<span style="font-family: Verdana">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 用户态使用createEvent()来创建并初始化一个event object。</span><br /><span style="font-family: Verdana">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 内核态使用KEVENT类型来创建一个event object，使用KeInitializeEventObject()来初始化一个event object。</span><br /><span style="font-family: Verdana">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: Verdana; color: red">一个event object有两种状态：nonsignaled和signaled</span><span style="font-family: Verdana">。使用</span><span style="font-family: Verdana; color: red">ResetEvent()</span><span style="font-family: Verdana">函数将event object的状态设置为nonsignaled。使用</span><span style="font-family: Verdana; color: red">SetEvent()</span><span style="font-family: Verdana">函数将event object的状态设置为signaled。</span><br /><span style="font-family: Verdana">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-family: Verdana; color: red">WaitForSingleObject</span><span style="font-family: Verdana; color: red">()</span><span style="font-family: Verdana">函数的参数是一个event object，当这个event object的状态为nonsignaled状态时，此函数会一直等待，一直到这个event object的状态变化为signaled，函数才会返回。</span><span style="font-family: Verdana; color: red">WaitForMultipleObjects()</span><span style="font-family: Verdana">函数的第三个参数是一个bool型变量，当为true的时候，那么等待的那几个event object的状态全部为signaled的时候，函数才返回；当为false的时候，那么等待的那几个event object中的任意一个的状态变为signaled，函数就返回。</span><br /><span style="font-family: Verdana">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 我觉得event object就是一个bool型的变量。<br />下面是今天晚上从书上摘抄的一段代码： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><span style="color: #000000">#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">windows.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">stddef.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">stdlib.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">conio.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">stdio.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" />ULONG&nbsp;WINAPI&nbsp;Thread1(LPVOID&nbsp;para)<br /><img id="Codehighlighter1_134_273_Open_Image" onclick="this.style.display='none'; Codehighlighter1_134_273_Open_Text.style.display='none'; Codehighlighter1_134_273_Closed_Image.style.display='inline'; Codehighlighter1_134_273_Closed_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img style="display: none" id="Codehighlighter1_134_273_Closed_Image" onclick="this.style.display='none'; Codehighlighter1_134_273_Closed_Text.style.display='none'; Codehighlighter1_134_273_Open_Image.style.display='inline'; Codehighlighter1_134_273_Open_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif"></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_134_273_Closed_Text"><img alt="" src="http://www.cppblog.com/Images/dot.gif" /></span><span id="Codehighlighter1_134_273_Open_Text"><span style="color: #000000">{<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #000000">"</span><span style="color: #000000">Enter&nbsp;Thread1!\n</span><span style="color: #000000">"</span><span style="color: #000000">);<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;HANDLE&nbsp;</span><span style="color: #000000">*</span><span style="color: #000000">phEvent&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;(HANDLE</span><span style="color: #000000">*</span><span style="color: #000000">)para;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">设置该事件激发</span><span style="color: #008000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;SetEvent(</span><span style="color: #000000">*</span><span style="color: #000000">phEvent);<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #000000">"</span><span style="color: #000000">Leave&nbsp;Thread1!\n</span><span style="color: #000000">"</span><span style="color: #000000">);<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">0</span><span style="color: #000000">;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockEnd.gif" />}</span></span><span style="color: #000000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif" /></span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;main()<br /><img id="Codehighlighter1_287_544_Open_Image" onclick="this.style.display='none'; Codehighlighter1_287_544_Open_Text.style.display='none'; Codehighlighter1_287_544_Closed_Image.style.display='inline'; Codehighlighter1_287_544_Closed_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img style="display: none" id="Codehighlighter1_287_544_Closed_Image" onclick="this.style.display='none'; Codehighlighter1_287_544_Closed_Text.style.display='none'; Codehighlighter1_287_544_Open_Image.style.display='inline'; Codehighlighter1_287_544_Open_Text.style.display='inline';" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif"></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_287_544_Closed_Text"><img alt="" src="http://www.cppblog.com/Images/dot.gif" /></span><span id="Codehighlighter1_287_544_Open_Text"><span style="color: #000000">{<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">创建同步事件</span><span style="color: #008000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;HANDLE&nbsp;hEvent&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;CreateEvent(NULL,FALSE,FALSE,NULL);<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">开启新线程，并将同步事件句柄指针传递给新线程</span><span style="color: #008000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;HANDLE&nbsp;hThread1&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;(HANDLE)CreateThread(NULL,</span><span style="color: #000000">0</span><span style="color: #000000">,Thread1,</span><span style="color: #000000">&amp;</span><span style="color: #000000">hEvent,</span><span style="color: #000000">0</span><span style="color: #000000">,NULL);<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">等待该事件触发</span><span style="color: #008000"><br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;WaitForSingleObject(hEvent,INFINITE);<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #000000">"</span><span style="color: #000000">Leave&nbsp;ThreadMain!\n</span><span style="color: #000000">"</span><span style="color: #000000">);<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: Verdana; color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">0</span><span style="color: #000000">;<br /><img alt="" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockEnd.gif" />}</span></span></div>代码的运行结果比较奇怪，如下图所示：<br /><img border="0" alt="" src="http://www.cppblog.com/images/cppblog_com/canmeng50401/CreateEvent运行结果1.JPG" width="669" height="439" /><br />我不懂为什么&#8220;Leave Thread1&#8221;会打印两次。应该只打印一次就结束的啊???&#8230;&#8230;&#8230;</span> <img src ="http://www.cppblog.com/canmeng50401/aggbug/146885.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/canmeng50401/" target="_blank">纪灿萌</a> 2011-05-21 22:45 <a href="http://www.cppblog.com/canmeng50401/articles/event_object.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>