﻿<?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博客-xianglj-文章分类-VC文章</title><link>http://www.cnitblog.com/xianglj/category/1427.html</link><description /><language>zh-cn</language><lastBuildDate>Sun, 02 Oct 2011 15:46:39 GMT</lastBuildDate><pubDate>Sun, 02 Oct 2011 15:46:39 GMT</pubDate><ttl>60</ttl><item><title>VC++ 6.0 中如何使用 CRT 调试功能来检测内存泄漏</title><link>http://www.cnitblog.com/xianglj/articles/4794.html</link><dc:creator>模式识别技术</dc:creator><author>模式识别技术</author><pubDate>Tue, 22 Nov 2005 02:23:00 GMT</pubDate><guid>http://www.cnitblog.com/xianglj/articles/4794.html</guid><wfw:comment>http://www.cnitblog.com/xianglj/comments/4794.html</wfw:comment><comments>http://www.cnitblog.com/xianglj/articles/4794.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/xianglj/comments/commentRss/4794.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/xianglj/services/trackbacks/4794.html</trackback:ping><description><![CDATA[最近看了周星星 Blog 中的一篇文章：“<A href="http://blog.vckbase.com/bruceteen/archive/2004/10/28/1130.<a%20href=" target=_blank http: www.chinaitpower.com Dev Web Asp index.html?>asp</A>x"&gt;<A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A>++6.0中内存泄漏检测</A>”，受益匪浅，便运行其例子代码想看看 Output 窗口中的输出结果，可惜怎么弄其输出都不是预期的东西，郁闷了半天，便到水坛里找到周星星，请求他指点一、二，然而未果。没有办法，最后我一头栽进 MSDN 库狂搜了一把，功夫不负有心人，我搜出很多有关这方面的资料，没过多久我便基本上就找到了答案......<BR>　　首先，检测内存泄漏的基本工具是调试器和 CRT 调试堆函数。为了使用调试堆函数，必须在要检测内存泄漏和调试的<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中添加下面的语句： 
<P></P><PRE>#define _CRTDBG_MAP_ALLOC 
#include&lt;stdlib.h&gt; 
#include&lt;crtdbg.h&gt; 

#include "debug_new.h" </PRE>
<P>　　MSDN 如是说：“必须保证上面声明的顺序，如果改变了顺序，可能不能正常工作。”至于这是为什么，我们不得而知。MS 的老大们经常这样故弄玄虚。<BR>　　针对非 MFC <A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>，再加上周星星的头<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>：debug_new.h，当然如果不加这一句，也能检测出内存泄漏，但是你无法确定在哪个源<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A><A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>中发生泄漏。Output 输出只告诉你在 crtsdb.h 中的某个地方有内存泄漏。我测试时 REG_DEBUG_NEW 没有起作用。加不加这个宏都可以检测出发生内存分配泄漏的<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>。<BR>　　其次，一旦添加了上面的声明，你就可以通过在<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中加入下面的代码来报告内存泄漏信息了：<BR></P><PRE>      _CrtDumpMemoryLeaks(); </PRE>　　这就这么简单。我在周星星的例子代码中加入这些机关后，在 <A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A>++ 调试会话（按 F5 调试运行） Output 窗口的 Debug 页便看到了预期的内存泄漏 dump。该 dump 形式如下：<PRE>Detected memory leaks! 
Dumping objects -&gt; 
c:\Program Files\...\include\crtdbg.h(552) : {45} normal block at 0x00441BA0, 2 bytes long. 
Data: &lt;AB&gt; 41 42 
c:\Program Files\...\include\crtdbg.h(552) : {44} normal block at 0x00441BD0, 33 bytes long. 
Data: &lt; C &gt; 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD 
c:\Program Files\...\include\crtdbg.h(552) : {43} normal block at 0x00441C20, 40 bytes long. 
Data: &lt; C &gt; E8 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00 
Object dump complete. </PRE>
<P>更具体的细节请参考本文附带的源代码<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>。<BR><BR>　　下面是我看过 MSDN 资料后，针对“如何使用 CRT 调试功能来检测内存泄漏？”的问题进行了一番编译和整理，希望对大家有用。如果你的英文很棒，那就不用往下看了，建议直接去读 MSDN 库中的技术原文。<BR>　　C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存，但是中国有句古话：“最大的长处也可能成为最大的弱点”，那么 C/C++ 应用<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>正好印证了这句话。在 C/C++ 应用<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>开发过程中，动态分配的内存处理不当是最常见的问题。其中，最难捉摸也最难检测的错误之一就是内存泄漏，即未能正确释放以前分配的内存的错误。偶尔发生的少量内存泄漏可能不会引起我们的注意，但泄漏大量内存的<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>或泄漏日益增多的<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>可能会表现出各种 各样的征兆：从性能不良（并且逐渐降低）到内存完全耗尽。更糟的是，泄漏的<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>可能会用掉太多内存，导致另外一个<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>垮掉，而使用户无从查找问题的真正根源。此外，即使无害的内存泄漏也可能殃及池鱼。<BR>　　幸运的是，Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法。下面请和我一起分享收获——如何使用 CRT 调试功能来检测内存泄漏？</P>
<OL>
<LI><A href="http://www.chinaitpower.com/A/2005-07-20/165113.html#如何启用内存泄漏检测机制">如何启用内存泄漏检测机制？</A> 
<UL>
<LI><A href="http://www.chinaitpower.com/A/2005-07-20/165113.html#使用 _CrtSetDbgFlag">使用 _CrtSetDbgFlag</A> 
<LI><A href="http://www.chinaitpower.com/A/2005-07-20/165113.html#设置 CRT 报告模式">设置 CRT 报告模式</A></LI></UL>
<LI><A href="http://www.chinaitpower.com/A/2005-07-20/165113.html#解释内存块类型">解释内存块类型</A> 
<LI><A href="http://www.chinaitpower.com/A/2005-07-20/165113.html#如何在内存分配序号处设置断点？">如何在内存分配序号处设置断点？</A> 
<LI><A href="http://www.chinaitpower.com/A/2005-07-20/165113.html#如何比较内存状态？">如何比较内存状态？</A> 
<LI><A href="http://www.chinaitpower.com/A/2005-07-20/165113.html#结论">结论</A> </LI></OL>
<P><B><A name=如何启用内存泄漏检测机制>如何启用内存泄漏检测机制</A>？</B><BR><BR>　　<A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A>++ IDE 的默认状态是没有启用内存泄漏检测机制的，也就是说即使某段代码有内存泄漏，调试会话的 Output 窗口的 Debug 页不会输出有关内存泄漏信息。你必须设定两个最基本的机关来启用内存泄漏检测机制。<BR><BR>一是使用调试堆函数：</P><PRE>#define _CRTDBG_MAP_ALLOC 
#include&lt;stdlib.h&gt; 
#include&lt;crtdbg.h&gt; </PRE>
<P>注意：#include 语句的顺序。如果更改此顺序，所使用的函数可能无法正确工作。<BR><BR>　　通过包含 crtdbg.h 头<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>，可以将 malloc 和 free 函数映射到其“调试”版本 _malloc_dbg 和 _free_dbg，这些函数会跟踪内存分配和释放。此映射只在调试（Debug）版本（也就是要定义 _DEBUG）中有效。发行版本（Release）使用普通的 malloc 和 free 函数。<BR>　　#define 语句将 CRT 堆函数的基础版本映射到对应的“调试”版本。该语句不是必须的，但如果没有该语句，那么有关内存泄漏的信息会不全。<BR><BR>二是在需要检测内存泄漏的地方添加下面这条语句来输出内存泄漏信息：</P><PRE>_CrtDumpMemoryLeaks();</PRE>　　当在调试器下运行<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>时，_CrtDumpMemoryLeaks 将在 Output 窗口的 Debug 页中显示内存泄漏信息。比如： <PRE>Detected memory leaks!
Dumping objects -&gt;
C:\Temp\memleak\memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long.
Data: &lt;AB&gt; 41 42 
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {44} normal block at 0x00441BD0, 33 bytes long.
Data: &lt; C &gt; 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD 
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {43} normal block at 0x00441C20, 40 bytes long.
Data: &lt; C &gt; 08 02 43 00 16 00 00 00 00 00 00 00 00 00 00 00 
Object dump complete.</PRE>
<P>如果不使用 #define _CRTDBG_MAP_ALLOC 语句，内存泄漏的输出是这样的：</P><PRE>Detected memory leaks!
Dumping objects -&gt;
{45} normal block at 0x00441BA0, 2 bytes long.
Data: &lt;AB&gt; 41 42 
{44} normal block at 0x00441BD0, 33 bytes long.
Data: &lt; C &gt; 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD 
{43} normal block at 0x00441C20, 40 bytes long.
Data: &lt; C &gt; C0 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00 
Object dump complete.</PRE>　　根据这段输出信息，你无法知道在哪个源<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A><A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>里发生了内存泄漏。下面我们来研究一下输出信息的格式。第一行和第二行没有什么可说的，从第三行开始： 
<P></P><PRE>xx}：花括弧内的数字是内存分配序号，本文例子中是 {45}，{44}，{43}；
block：内存块的类型，常用的有三种：normal（普通）、client（客户端）或 CRT（运行时）；本文例子中是：normal block； 
用十六进制格式表示的内存位置，如：at 0x00441BA0 等；
以字节为单位表示的内存块的大小，如：32 bytes long； 
前 16 字节的内容（也是用十六进制格式表示），如：Data: &lt;AB&gt; 41 42 等；</PRE>
<P>　　仔细观察不难发现，如果定义了 _CRTDBG_MAP_ALLOC ，那么在内存分配序号前面还会显示在其中分配泄漏内存的<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>名，以及<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>名后括号中的数字表示发生泄漏的代码行号，比如： </P><PRE>C:\Temp\memleak\memleak.cpp(15) </PRE>　　双击 Output 窗口中此<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>名所在的输出行，便可跳到源<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A><A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>分配该内存的代码行（也可以选中该行，然后按 F4，效果一样） ，这样一来我们就很容易定位内存泄漏是在哪里发生的了，因此，_CRTDBG_MAP_ALLOC 的作用显而易见。<BR><BR><I><B><A name="使用 _CrtSetDbgFlag">使用 _CrtSetDbgFlag</A></B></I><BR><BR>　　如果<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>只有一个出口，那么调用 _CrtDumpMemoryLeaks 的位置是很容易选择的。但是，如果<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>可能会在多个地方退出该怎么办呢？在每一个可能的出口处调用 _CrtDumpMemoryLeaks 肯定是不可取的，那么这时可以在<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>开始处包含下面的调用：<PRE>_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );</PRE>
<P>　　这条语句无论<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>在什么地方退出都会自动调用 _CrtDumpMemoryLeaks。注意：这里必须同时设置两个位域标志：_CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF。<BR><BR><I><B><A name="设置 CRT 报告模式">设置 CRT 报告模式</A></B></I><BR><BR>　　默认情况下，_CrtDumpMemoryLeaks 将内存泄漏信息 dump 到 Output 窗口的 Debug 页， 如果你想将这个输出定向到别的地方，可以使用 _CrtSetReportMode 进行重置。如果你使用某个库，它可能将输出定向到另一位置。此时，只要使用以下语句将输出位置设回 Output 窗口即可：</P><PRE>_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );</PRE>
<P>有关使用 _CrtSetReportMode 的详细信息，请参考 MSDN 库关于 _CrtSetReportMode 的描述。<BR><BR><B><A name=解释内存块类型>解释内存块类型</A></B><BR><BR>　　前面已经说过，内存泄漏报告中把每一块泄漏的内存分为 normal（普通块）、client（客户端块）和 CRT 块。事实上，需要留心和注意的也就是 normal 和 client，即普通块和客户端块。</P>
<UL>
<LI>normal block（普通块）：这是由你的<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>分配的内存。 
<LI>client block（客户块）：这是一种特殊类型的内存块，专门用于 MFC <A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中需要析构函数的对象。MFC new 操作符视具体情况既可以为所创建的对象建立普通块，也可以为之建立客户块。 
<LI>CRT block（CRT 块）：是由 C RunTime Library 供自己使用而分配的内存块。由 CRT 库自己来管理这些内存的分配与释放，我们一般不会在内存泄漏报告中发现 CRT 内存泄漏，除非<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>发生了严重的错误（例如 CRT 库崩溃）。 </LI></UL>
<P>除了上述的类型外，还有下面这两种类型的内存块，它们不会出现在内存泄漏报告中：</P>
<UL>
<LI>free block（空闲块）：已经被释放(free)的内存块。 
<LI>Ignore block（忽略块）：这是<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>员显式声明过不要在内存泄漏报告中出现的内存块。 </LI></UL>
<P><B><A name=如何在内存分配序号处设置断点？>如何在内存分配序号处设置断点？</A></B><BR><BR>　　在内存泄漏报告中，的<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>名和行号可告诉分配泄漏的内存的代码位置，但仅仅依赖这些信息来了解完整的泄漏原因是不够的。因为一个<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>在运行时，一段分配内存的代码可能会被调用很多次，只要有一次调用后没有释放内存就会导致内存泄漏。为了确定是哪些内存没有被释放，不仅要知道泄漏的内存是在哪里分配的，还要知道泄漏产生的条件。这时内存分配序号就显得特别有用——这个序号就是<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>名和行号之后的花括弧里的那个数字。<BR>　　例如，在本文例子代码的输出信息中，“45”是内存分配序号，意思是泄漏的内存是你<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中分配的第四十五个内存块：</P><PRE>Detected memory leaks!
Dumping objects -&gt;
C:\Temp\memleak\memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long.
Data: &lt;AB&gt; 41 42 
......
Object dump complete. </PRE>
<P>　　CRT 库对<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>运行期间分配的所有内存块进行计数，包括由 CRT 库自己分配的内存和其它库（如 MFC）分配的内存。因此，分配序号为 N 的对象即为<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中分配的第 N 个对象，但不一定是代码分配的第 N 个对象。（大多数情况下并非如此。）<BR>　　这样的话，你便可以利用分配序号在分配内存的位置设置一个断点。方法是在<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>起始附近设置一个位置断点。当<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>在该点中断时，可以从 QuickWatch（快速监视）对话框或 Watch（监视）窗口设置一个内存分配断点：<BR><BR>　　例如，在 Watch 窗口中，在 Name 栏键入下面的表达式：</P><PRE>_crtBreakAlloc</PRE>
<P>如果要使用 CRT 库的多线程 DLL 版本（/MD 选项），那么必须包含上下文操作符，像这样： </P><PRE>{,,msvcrtd.dll}_crtBreakAlloc</PRE>
<P>　　现在按下回车键，调试器将计算该值并把结果放入 Value 栏。如果没有在内存分配点设置任何断点，该值将为 –1。<BR>　　用你想要在其位置中断的内存分配的分配序号替换 Value 栏中的值。例如输入 45。这样就会在分配序号为 45 的地方中断。 <BR>　　在所感兴趣的内存分配处设置断点后，可以继续调试。这时，运行<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>时一定要小心，要保证内存块分配的顺序不会改变。当<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>在指定的内存分配处中断时，可以查看 Call Stack（调用堆栈）窗口和其它调试器信息以确定分配内存时的情况。如果必要，可以从该点继续执行<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>，以查看对象发生了什么情况，或许可以确定未正确释放对象的原因。<BR>　　尽管通常在调试器中设置内存分配断点更方便，但如果愿意，也可在代码中设置这些断点。为了在代码中设置一个内存分配断点，可以增加这样一行（对于第四十五个内存分配）：</P><PRE>_crtBreakAlloc = 45;</PRE>
<P>你还可以使用有相同效果的 _CrtSetBreakAlloc 函数：</P><PRE>_CrtSetBreakAlloc(45);</PRE>
<P><B><A name=如何比较内存状态？>如何比较内存状态？</A></B><BR><BR>　　定位内存泄漏的另一个方法就是在关键点获取应用<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>内存状态的快照。CRT 库提供了一个结构类型 _CrtMemState。你可以用它来存储内存状态的快照：</P><PRE>_CrtMemState s1, s2, s3;</PRE>
<P>　　若要获取给定点的内存状态快照，可以向 _CrtMemCheckpoint 函数传递一个 _CrtMemState 结构。该函数用当前内存状态的快照填充此结构：</P><PRE>_CrtMemCheckpoint( &amp;s1 );</PRE>
<P>　　通过向 _CrtMemDumpStatistics 函数传递 _CrtMemState 结构，可以在任意地方 dump 该结构的内容：</P><PRE>_CrtMemDumpStatistics( &amp;s1 );</PRE>
<P>该函数输出如下格式的 dump 内存分配信息：</P><PRE>0 bytes in 0 Free Blocks.
75 bytes in 3 Normal Blocks.
5037 bytes in 41 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 5308 bytes.
Total allocations: 7559 bytes.</PRE>
<P>　　若要确定某段代码中是否发生了内存泄漏，可以通过获取该段代码之前和之后的内存状态快照，然后使用 _CrtMemDifference 比较这两个状态：</P><PRE>_CrtMemCheckpoint( &amp;s1 );// 获取第一个内存状态快照

// 在这里进行内存分配

_CrtMemCheckpoint( &amp;s2 );// 获取第二个内存状态快照

// 比较两个内存快照的差异
if ( _CrtMemDifference( &amp;s3, &amp;s1, &amp;s2) )
     _CrtMemDumpStatistics( &amp;s3 );// dump 差异结果</PRE>
<P>　　顾名思义，_CrtMemDifference 比较两个内存状态（前两个参数），生成这两个状态之间差异的结果（第三个参数）。在<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的开始和结尾放置 _CrtMemCheckpoint 调用，并使用 _CrtMemDifference 比较结果，是检查内存泄漏的另一种方法。如果检测到泄漏，则可以使用 _CrtMemCheckpoint 调用通过二进制搜索技术来分割<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>和定位泄漏。<BR><BR><B><A name=结论>结论</A></B><BR><BR>　　尽管 <A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A> ++ 具有一套专门调试 MFC 应用<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的机制，但本文上述讨论的内存分配很简单，没有涉及到 MFC 对象，所以这些内容同样也适用于 MFC <A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>。在 MSDN 库中可以找到很多有关 <A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A>++ 调试方面的资料，如果你能善用 MSDN 库，相信用不了多少时间你就有可能成为调试高手。<BR><BR>本人水平不高，谬误在所难免，请大家拍砖，不要客气。顺祝大家圣诞快乐</P><img src ="http://www.cnitblog.com/xianglj/aggbug/4794.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/xianglj/" target="_blank">模式识别技术</a> 2005-11-22 10:23 <a href="http://www.cnitblog.com/xianglj/articles/4794.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC Studio 使用技巧大全</title><link>http://www.cnitblog.com/xianglj/articles/4793.html</link><dc:creator>模式识别技术</dc:creator><author>模式识别技术</author><pubDate>Tue, 22 Nov 2005 02:22:00 GMT</pubDate><guid>http://www.cnitblog.com/xianglj/articles/4793.html</guid><wfw:comment>http://www.cnitblog.com/xianglj/comments/4793.html</wfw:comment><comments>http://www.cnitblog.com/xianglj/articles/4793.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/xianglj/comments/commentRss/4793.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/xianglj/services/trackbacks/4793.html</trackback:ping><description><![CDATA[<TABLE class=big cellSpacing=0 borderColorDark=#ffffff cellPadding=0 width="100%" borderColorLight=#3399ff border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#cccccc>1.检测<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中的括号是否匹配</TD></TR>
<TR>
<TD width="100%">&nbsp; 把光标移动到需要检测的括号（如大括号{}、方括号[]、圆括号（）和尖括号&lt;&gt;）前面，键入快捷键“Ctrl＋]”。如果括号匹配正确，光标就跳到匹配的括号处，否则光标不移动，并且机箱喇叭还会发出一声警告声。<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>2.查看一个宏（或变量、函数）的宏定义</TD></TR>
<TR>
<TD width="100%">&nbsp; 把光标移动到你想知道的一个宏上，就比如说最常见的DECLARE_MAP_MESSAGE上按一下F12(或右键菜单中的Go To Defition Of …),如果没有建立Browse files，会出现提示对话框，确定，然后就会跳到定义那些东西的地方。<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>3.格式化一段乱七八糟的源代码</TD></TR>
<TR>
<TD width="100%">&nbsp; 选中那段源代码，按ATL+F8。<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc height=15>4.在编辑状态下发现成员变量或函数不能显示</TD></TR>
<TR>
<TD width="100%">&nbsp; 删除该项目扩展名为.ncb<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>，重新打开该项目。<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>5.如何整理ClassView视图中大量的类</TD></TR>
<TR>
<TD width="100%">&nbsp; 可以在classview 视图中右键新建<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>夹（new folder），再把具有相近性质的类拖到对应的<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>夹中，使整个视图看上去清晰明了<BR>.<BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>6.定位预处理指定</TD></TR>
<TR>
<TD width="100%">在源<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>中定位光标到对称的#if, #endif,使用Ctrl+K.<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>7.如何添加<A href="http://www.chinaitpower.com/System/index.html" target=_blank>系统</A>中Lib到当前项目</TD></TR>
<TR>
<TD width="100%">&nbsp; 在Project | Settings | Link | Object/library modules：输入Lib名称，不同的Lib之间用空格格开.<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>8.如何添加<A href="http://www.chinaitpower.com/System/index.html" target=_blank>系统</A>中的头<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>(.h)到当前项目.</TD></TR>
<TR>
<TD width="100%">&nbsp; #include &lt;FileName.h&gt;,告诉编译到<A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A><A href="http://www.chinaitpower.com/System/index.html" target=_blank>系统</A>目录去找;使用#include "FileName.h"，告诉编译在当前目录找.<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>9.如何在Studio使用汇编调试</TD></TR>
<TR>
<TD width="100%">&nbsp; 在WorkBench的Debugger状态下按CTRL+F7.<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>10.怎样处理ClassZiard找不到的<A href="http://www.chinaitpower.com/System/index.html" target=_blank>系统</A>消息</TD></TR>
<TR>
<TD width="100%">&nbsp; 如果要在ClassWizard中处理WM_NCHITTEST等<A href="http://www.chinaitpower.com/System/index.html" target=_blank>系统</A>消息，请在ClassWizard中Class Info页中将Message filter改为Window就有了.<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>11.如何干净的删除一个类</TD></TR>
<TR>
<TD width="100%">&nbsp; 先从Workspace中的FileView中删除对应的.h和.cpp<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,再关闭项目，从实际的<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>夹中删除对应的.h和.cpp<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>与.clw<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>。<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>12.如果让控制台应用<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>支持mfc类库</TD></TR>
<TR>
<TD width="100%">&nbsp; 可以在控制台应用<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中include 来引入mfc库，但是控制台应用<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>缺省是单线程的，mfc是多线程的，为解决该矛盾，在project setting-&gt;c/c++ 选项，选择code generation,在use run-time library 下拉框中选择debug multithread。<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>13.如何汉化只有可执行代码的.exe <A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A></TD></TR>
<TR>
<TD width="100%">&nbsp; 在nt 下利用vc open file 以resources方式打开*.exe <A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>，直接修改资源<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>，然后保存即可。<BR><BR></TD></TR>
<TR>
<TD width="100%" bgColor=#cccccc>附：<A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A>项目<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>说明</TD></TR>
<TR>
<TD vAlign=top width="100%">
<TABLE class=small cellSpacing=1 cellPadding=0 width="100%" bgColor=#cccccc border=0>
<TBODY>
<TR bgColor=#ffffff>
<TD width="14%">.opt</TD>
<TD width="86%">工程关于开发环境的参数<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>。如工具条位置等信息；</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.aps</TD>
<TD width="86%">(AppStudio File),资源辅助<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,二进制格式,一般不用去管他.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.clw</TD>
<TD width="86%">ClassWizard信息<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,实际上是INI<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>的格式,有兴趣可以研究一下.有时候ClassWizard出问题,手工修改CLW<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>可以解决.如果此<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>不存在的话,每次用ClassWizard的时候绘提示你是否重建.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.dsp</TD>
<TD width="86%">(DeveloperStudio Project):项目<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,文本格式,不过不熟悉的话不要手工修改.DSW(DeveloperStudio Workspace)是工作区<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,其他特点和DSP差不多.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.plg</TD>
<TD width="86%">是编译信息<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,编译时的error和warning信息<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>（实际上是一个html<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>）,一般用处不大.在Tools-&gt;Options里面有个选项可以控制这个<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>的生成.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.hpj</TD>
<TD width="86%">(Help Project)是生成帮助<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>的工程,用microsfot&nbsp; Help Compiler可以处理.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.mdp</TD>
<TD width="86%">(Microsoft DevStudio Project)是旧版本的项目<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,如果要打开此<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>的话,会提示你是否转换成新的DSP格式.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.bsc</TD>
<TD width="86%">是用于浏览项目信息的,如果用Source Brower的话就必须有这个<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>.如果不用这个功能的话,可以在Project Options里面去掉Generate Browse Info File,可以加快编译速度.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.map</TD>
<TD width="86%">是执行<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>的映像信息纪录<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,除非对<A href="http://www.chinaitpower.com/System/index.html" target=_blank>系统</A>底层非常熟悉,这个<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>一般用不着.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.pch</TD>
<TD width="86%">(Pre-Compiled File)是预编译<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>,可以加快编译速度,但是<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>非常大.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.pdb</TD>
<TD width="86%">(Program Database)记录了<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>有关的一些数据和调试信息,在调试的时候可能有用.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.exp</TD>
<TD width="86%">只有在编译DLL的时候才会生成,记录了DLL<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>中的一些信息.一般也没什么用.</TD></TR>
<TR bgColor=#ffffff>
<TD width="14%">.ncb</TD>
<TD width="86%">无编译浏览<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>(no compile browser)。当自动完成功能出问题时可以删除此<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>。build后会自动生成。</TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.cnitblog.com/xianglj/aggbug/4793.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/xianglj/" target="_blank">模式识别技术</a> 2005-11-22 10:22 <a href="http://www.cnitblog.com/xianglj/articles/4793.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>仅通过崩溃地址找出源代码的出错行</title><link>http://www.cnitblog.com/xianglj/articles/4792.html</link><dc:creator>模式识别技术</dc:creator><author>模式识别技术</author><pubDate>Tue, 22 Nov 2005 02:20:00 GMT</pubDate><guid>http://www.cnitblog.com/xianglj/articles/4792.html</guid><wfw:comment>http://www.cnitblog.com/xianglj/comments/4792.html</wfw:comment><comments>http://www.cnitblog.com/xianglj/articles/4792.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/xianglj/comments/commentRss/4792.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/xianglj/services/trackbacks/4792.html</trackback:ping><description><![CDATA[作为程序员，我们平时最担心见到的事情是什么？是内存泄漏？是界面不好看？……错啦！我相信我的看法是不会有人反对的——那就是，程序发生了崩溃！ 
<P>“该程序执行了非法操作，即将关闭。请与你的软件供应商联系。”，呵呵，这句 M$ 的“名言”，恐怕就是程序员最担心见到的东西了。有的时候，自己的程序在自己的机器上运行得好好的，但是到了别人的机器上就崩溃了；有时自己在编写和测试的过程中就莫名其妙地遇到了非法操作，但是却无法确定到底是源代码中的哪行引起的……是不是很痛苦呢？不要紧，本文可以帮助你走出这种困境，甚至你从此之后可以自豪地要求用户把崩溃地址告诉你，然后你就可以精确地定位到源代码中出错的那行了。（很神奇吧？呵呵。）</P>
<P>首先我必须强调的是，本方法可以在目前市面上任意一款编译器上面使用。但是我只熟悉 M$ 的 VC 和 MASM ，因此后面的部分只介绍如何在这两个编译器中实现，请读者自行融会贯通，掌握在别的编译器上使用的方法。</P>
<P>Well，废话说完了，让我们开始！ ：）</P>
<P>首先必须生成程序的 MAP 文件。什么是 MAP 文件？简单地讲， MAP 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法，它可以在任何地方、任何时候使用，不需要有额外的程序进行支持。而且，这是唯一能找出程序崩溃的地方的救星。</P>
<P>好吧，既然 MAP 文件如此神奇，那么我们应该如何生成它呢？在 VC 中，我们可以按下 Alt+F7 ，打开“Project Settings”选项页，选择 C/C++ 选项卡，并在最下面的 Project Options 里面输入：/Zd ，然后要选择 Link 选项卡，在最下面的 Project Options 里面输入： /mapinfo:lines 和 /map:PROJECT_NAME.map 。最后按下 F7 来编译生成 EXE 可执行文件和 MAP 文件。</P>
<P>在 MASM 中，我们要设置编译和连接参数，我通常是这样做的：</P>
<P>rc %1.rc<BR>ml /c /coff /Zd %1.asm<BR>link /subsystem:windows /mapinfo:exports /mapinfo:lines /map:%1.map %1.obj %1.res </P>
<P>把它保存成 makem.bat ，就可以在命令行输入 makem filename 来编译生成 EXE 可执行文件和 MAP 文件了。</P>
<P>在此我先解释一下加入的参数的含义：</P>
<P>/Zd 表示在编译的时候生成行信息<BR>/map[:filename] 表示生成 MAP 文件的路径和文件名<BR>/mapinfo:lines 表示生成 MAP 文件时，加入行信息<BR>/mapinfo:exports 表示生成 MAP 文件时，加入 exported functions （如果生成的是 DLL 文件，这个选项就要加上） </P>
<P>OK，通过上面的步骤，我们已经得到了 MAP 文件，那么我们该如何利用它呢？</P>
<P>让我们从简单的实例入手，请打开你的 VC ，新建这样一个文件：</P>
<P>01 //****************************************************************<BR>02 //程序名称：演示如何通过崩溃地址找出源代码的出错行<BR>03 //作者：罗聪<BR>04 //日期：2003-2-7<BR>05 //出处：http://www.luocong.com（老罗的缤纷天地）<BR>06 //本程序会产生“除0错误”，以至于会弹出“非法操作”对话框。<BR>07 //“除0错误”只会在 Debug 版本下产生，本程序为了演示而尽量简化。<BR>08 //注意事项：如欲转载，请保持本程序的完整，并注明：<BR>09 //转载自“老罗的缤纷天地”（http://www.luocong.com）<BR>10 //****************************************************************<BR>11 <BR>12 void Crash(void)<BR>13 {<BR>14 int i = 1;<BR>15 int j = 0;<BR>16 i /= j;<BR>17 }<BR>18 <BR>19 void main(void)<BR>20 {<BR>21 Crash();<BR>22 } </P>
<P>很显然本程序有“除0错误”，在 Debug 方式下编译的话，运行时肯定会产生“非法操作”。好，让我们运行它，果然，“非法操作”对话框出现了，这时我们点击“详细信息”按钮，记录下产生崩溃的地址——在我的机器上是 0x0040104a 。</P>
<P>再看看它的 MAP 文件：（由于文件内容太长，中间没用的部分我进行了省略）</P>
<P>CrashDemo</P>
<P>Timestamp is 3e430a76 (Fri Feb 07 09:23:02 2003)</P>
<P>Preferred load address is 00400000</P>
<P>Start Length Name Class<BR>0001:00000000 0000de04H .text CODE<BR>0001:0000de04 0001000cH .textbss CODE<BR>0002:00000000 00001346H .rdata DATA<BR>0002:00001346 00000000H .edata DATA<BR>0003:00000000 00000104H .CRT$XCA DATA<BR>0003:00000104 00000104H .CRT$XCZ DATA<BR>0003:00000208 00000104H .CRT$XIA DATA<BR>0003:0000030c 00000109H .CRT$XIC DATA<BR>0003:00000418 00000104H .CRT$XIZ DATA<BR>0003:0000051c 00000104H .CRT$XPA DATA<BR>0003:00000620 00000104H .CRT$XPX DATA<BR>0003:00000724 00000104H .CRT$XPZ DATA<BR>0003:00000828 00000104H .CRT$XTA DATA<BR>0003:0000092c 00000104H .CRT$XTZ DATA<BR>0003:00000a30 00000b93H .data DATA<BR>0003:000015c4 00001974H .bss DATA<BR>0004:00000000 00000014H .idata$2 DATA<BR>0004:00000014 00000014H .idata$3 DATA<BR>0004:00000028 00000110H .idata$4 DATA<BR>0004:00000138 00000110H .idata$5 DATA<BR>0004:00000248 000004afH .idata$6 DATA</P>
<P>Address Publics by Value Rva+Base Lib:Object</P>
<P>0001:00000020 ?Crash@@YAXXZ 00401020 f CrashDemo.obj<BR>0001:00000070 _main 00401070 f CrashDemo.obj<BR>0004:00000000 __IMPORT_DESCRIPTOR_KERNEL32 00424000 kernel32:KERNEL32.dll<BR>0004:00000014 __NULL_IMPORT_DESCRIPTOR 00424014 kernel32:KERNEL32.dll<BR>0004:00000138 __imp__GetCommandLineA@0 00424138 kernel32:KERNEL32.dll<BR>0004:0000013c __imp__GetVersion@0 0042413c kernel32:KERNEL32.dll<BR>0004:00000140 __imp__ExitProcess@4 00424140 kernel32:KERNEL32.dll<BR>0004:00000144 __imp__DebugBreak@0 00424144 kernel32:KERNEL32.dll<BR>0004:00000148 __imp__GetStdHandle@4 00424148 kernel32:KERNEL32.dll<BR>0004:0000014c __imp__WriteFile@20 0042414c kernel32:KERNEL32.dll<BR>0004:00000150 __imp__InterlockedDecrement@4 00424150 kernel32:KERNEL32.dll<BR>0004:00000154 __imp__OutputDebugStringA@4 00424154 kernel32:KERNEL32.dll<BR>0004:00000158 __imp__GetProcAddress@8 00424158 kernel32:KERNEL32.dll<BR>0004:0000015c __imp__LoadLibraryA@4 0042415c kernel32:KERNEL32.dll<BR>0004:00000160 __imp__InterlockedIncrement@4 00424160 kernel32:KERNEL32.dll<BR>0004:00000164 __imp__GetModuleFileNameA@12 00424164 kernel32:KERNEL32.dll<BR>0004:00000168 __imp__TerminateProcess@8 00424168 kernel32:KERNEL32.dll<BR>0004:0000016c __imp__GetCurrentProcess@0 0042416c kernel32:KERNEL32.dll<BR>0004:00000170 __imp__UnhandledExceptionFilter@4 00424170 kernel32:KERNEL32.dll<BR>0004:00000174 __imp__FreeEnvironmentStringsA@4 00424174 kernel32:KERNEL32.dll<BR>0004:00000178 __imp__FreeEnvironmentStringsW@4 00424178 kernel32:KERNEL32.dll<BR>0004:0000017c __imp__WideCharToMultiByte@32 0042417c kernel32:KERNEL32.dll<BR>0004:00000180 __imp__GetEnvironmentStrings@0 00424180 kernel32:KERNEL32.dll<BR>0004:00000184 __imp__GetEnvironmentStringsW@0 00424184 kernel32:KERNEL32.dll<BR>0004:00000188 __imp__SetHandleCount@4 00424188 kernel32:KERNEL32.dll<BR>0004:0000018c __imp__GetFileType@4 0042418c kernel32:KERNEL32.dll<BR>0004:00000190 __imp__GetStartupInfoA@4 00424190 kernel32:KERNEL32.dll<BR>0004:00000194 __imp__HeapDestroy@4 00424194 kernel32:KERNEL32.dll<BR>0004:00000198 __imp__HeapCreate@12 00424198 kernel32:KERNEL32.dll<BR>0004:0000019c __imp__HeapFree@12 0042419c kernel32:KERNEL32.dll<BR>0004:000001a0 __imp__VirtualFree@12 004241a0 kernel32:KERNEL32.dll<BR>0004:000001a4 __imp__RtlUnwind@16 004241a4 kernel32:KERNEL32.dll<BR>0004:000001a8 __imp__GetLastError@0 004241a8 kernel32:KERNEL32.dll<BR>0004:000001ac __imp__SetConsoleCtrlHandler@8 004241ac kernel32:KERNEL32.dll<BR>0004:000001b0 __imp__IsBadWritePtr@8 004241b0 kernel32:KERNEL32.dll<BR>0004:000001b4 __imp__IsBadReadPtr@8 004241b4 kernel32:KERNEL32.dll<BR>0004:000001b8 __imp__HeapValidate@12 004241b8 kernel32:KERNEL32.dll<BR>0004:000001bc __imp__GetCPInfo@8 004241bc kernel32:KERNEL32.dll<BR>0004:000001c0 __imp__GetACP@0 004241c0 kernel32:KERNEL32.dll<BR>0004:000001c4 __imp__GetOEMCP@0 004241c4 kernel32:KERNEL32.dll<BR>0004:000001c8 __imp__HeapAlloc@12 004241c8 kernel32:KERNEL32.dll<BR>0004:000001cc __imp__VirtualAlloc@16 004241cc kernel32:KERNEL32.dll<BR>0004:000001d0 __imp__HeapReAlloc@16 004241d0 kernel32:KERNEL32.dll<BR>0004:000001d4 __imp__MultiByteToWideChar@24 004241d4 kernel32:KERNEL32.dll<BR>0004:000001d8 __imp__LCMapStringA@24 004241d8 kernel32:KERNEL32.dll<BR>0004:000001dc __imp__LCMapStringW@24 004241dc kernel32:KERNEL32.dll<BR>0004:000001e0 __imp__GetStringTypeA@20 004241e0 kernel32:KERNEL32.dll<BR>0004:000001e4 __imp__GetStringTypeW@16 004241e4 kernel32:KERNEL32.dll<BR>0004:000001e8 __imp__SetFilePointer@16 004241e8 kernel32:KERNEL32.dll<BR>0004:000001ec __imp__SetStdHandle@8 004241ec kernel32:KERNEL32.dll<BR>0004:000001f0 __imp__FlushFileBuffers@4 004241f0 kernel32:KERNEL32.dll<BR>0004:000001f4 __imp__CloseHandle@4 004241f4 kernel32:KERNEL32.dll<BR>0004:000001f8 \177KERNEL32_NULL_THUNK_DATA 004241f8 kernel32:KERNEL32.dll</P>
<P>entry point at 0001:000000f0</P>
<P><BR>Line numbers for .\Debug\CrashDemo.obj(d:\msdev\myprojects\crashdemo\crashdemo.cpp) segment .text</P>
<P>13 0001:00000020 14 0001:00000038 15 0001:0000003f 16 0001:00000046<BR>17 0001:00000050 20 0001:00000070 21 0001:00000088 22 0001:0000008d </P>
<P>如果仔细浏览 Rva+Base 这栏，你会发现第一个比崩溃地址 0x0040104a 大的函数地址是 0x00401070 ，所以在 0x00401070 这个地址之前的那个入口就是产生崩溃的函数，也就是这行：</P>
<P>0001:00000020 ?Crash@@YAXXZ 00401020 f CrashDemo.obj </P>
<P>因此，发生崩溃的函数就是 ?Crash@@YAXXZ ，所有以问号开头的函数名称都是 C++ 修饰的名称。在我们的源程序中，也就是 Crash() 这个子函数。</P>
<P>OK，现在我们轻而易举地便知道了发生崩溃的函数名称，你是不是很兴奋呢？呵呵，先别忙，接下来，更厉害的招数要出场了。</P>
<P>请注意 MAP 文件的最后部分——代码行信息（Line numbers information），它是以这样的形式显示的：</P>
<P>13 0001:00000020 </P>
<P>第一个数字代表在源代码中的代码行号，第二个数是该代码行在所属的代码段中的偏移量。</P>
<P>如果要查找代码行号，需要使用下面的公式做一些十六进制的减法运算：</P>
<P>崩溃行偏移 = 崩溃地址（Crash Address） - 基地址（ImageBase Address） - 0x1000 </P>
<P>为什么要这样做呢？细心的朋友可能会留意到 Rva+Base 这栏了，我们得到的崩溃地址都是由 偏移地址（Rva）+ 基地址（Base） 得来的，所以在计算行号的时候要把基地址减去，一般情况下，基地址的值是 0x00400000 。另外，由于一般的 PE 文件的代码段都是从 0x1000 偏移开始的，所以也必须减去 0x1000 。</P>
<P>好了，明白了这点，我们就可以来进行小学减法计算了：</P>
<P>崩溃行偏移 = 0x0040104a - 0x00400000 - 0x1000 = 0x4a </P>
<P>如果浏览 MAP 文件的代码行信息，会看到不超过计算结果，但却最接近的数是 CrashDemo.cpp 文件中的：</P>
<P>16 0001:00000046 </P>
<P>也就是在源代码中的第 16 行，让我们来看看源代码:</P>
<P>16 i /= j; </P>
<P>哈！！！果然就是第 16 行啊！</P>
<P>兴奋吗？我也一样！ ：）</P>
<P>方法已经介绍完了，从今以后，我们就可以精确地定位到源代码中的崩溃行，而且只要编译器可以生成 MAP 文件（包括 VC、MASM、VB、BCB、Delphi……），本方法都是适用的。我们时常抱怨 M$ 的产品如何如何差，但其实 M$ 还是有意无意间提供了很多有价值的信息给我们的，只是我们往往不懂得怎么利用而已……相信这样一来，你就可以更为从容地面对“非法操作”提示了。你甚至可以要求用户提供崩溃的地址，然后就可以坐在家中舒舒服服地找到出错的那行，并进行修正。</P>
<P>是不是很爽呢？ ：） <BR></P><img src ="http://www.cnitblog.com/xianglj/aggbug/4792.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/xianglj/" target="_blank">模式识别技术</a> 2005-11-22 10:20 <a href="http://www.cnitblog.com/xianglj/articles/4792.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++代码优化方法总结</title><link>http://www.cnitblog.com/xianglj/articles/4791.html</link><dc:creator>模式识别技术</dc:creator><author>模式识别技术</author><pubDate>Tue, 22 Nov 2005 02:18:00 GMT</pubDate><guid>http://www.cnitblog.com/xianglj/articles/4791.html</guid><wfw:comment>http://www.cnitblog.com/xianglj/comments/4791.html</wfw:comment><comments>http://www.cnitblog.com/xianglj/articles/4791.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/xianglj/comments/commentRss/4791.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/xianglj/services/trackbacks/4791.html</trackback:ping><description><![CDATA[优化是一个非常大的主题，本文并不是去深入探讨性能分析理论，算法的效率，况且我也没有这个能力。我只是想把一些可以简单的应用到你的C++代码中的优化技术总结在这里，这样，当你遇到几种不同的编程策略的时候，就可以对每种策略的性能进行一个大概的估计。这也是本文的目的之所在。<BR>　一. 优化之前<BR><BR>　　在进行优化之前，我们首先应该做的是发现我们代码的瓶颈（bottleneck）在哪里。然而当你做这件事情的时候切忌从一个debug-version进行推断，因为debug-version中包含了许多额外的代码。一个debug-version可执行体要比release-version大出40%。那些额外的代码都是用来支持调试的，比如说符号的查找。大多数实现都为debug-version和release-version提供了不同的operator new以及库函数。而且，一个release-version的执行体可能已经通过多种途径进行了优化，包括不必要的临时对象的消除，循环展开，把对象移入寄存器，内联等等。<BR><BR>　　另外，我们要把调试和优化区分开来，它们是在完成不同的任务。 debug-version 是用来追捕bugs以及检查<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>是否有逻辑上的问题。release-version则是用来做一些性能上的调整以及进行优化。<BR><BR>　　下面就让我们来看看有哪些代码优化技术吧：<BR><BR>　　二. 声明的放置<BR><BR>　　<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中变量和对象的声明放在什么位置将会对性能产生显著影响。同样，对postfix和prefix运算符的选择也会影响性能。这一部分我们集中讨论四个问题：初始化v.s 赋值，在<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>确实要使用的地方放置声明，构造函数的初始化列表，prefix v.s postfix运算符。<BR><BR>　　（1）请使用初始化而不是赋值<BR><BR>　　在C语言中只允许在一个函数体的开头进行变量的声明，然而在C++中声明可以出现在<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的任何位置。这样做的目的是希望把对象的声明拖延到确实要使用它的时候再进行。这样做可以有两个好处：1. 确保了对象在它被使用前不会被<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的其他部分恶意修改。如果对象在开头就被声明然而却在20行以后才被使用的话，就不能做这样的保证。2. 使我们有机会通过用初始化取代赋值来达到性能的提升，从前声明只能放在开头，然而往往开始的时候我们还没有获得我们想要的值，因此初始化所带来的好处就无法被应用。但是现在我们可以在我们获得了想要的值的时候直接进行初始化，从而省去了一步。注意，或许对于基本类型来说，初始化和赋值之间可能不会有什么差异，但是对于用户定义的类型来说，二者就会带来显著的不同，因为赋值会多进行一次函数调用----operator =。因此当我们在赋值和初始化之间进行选择的话，初始化应该是我们的首选。<BR>
<DIV align=center twffan="done"><SPAN id=zl_550 style="HEIGHT: 60px" twffan="done">
<OBJECT codeBase=http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=5,0,0,0 height=60 width=410 classid=clsid:D27CDB6E-AE6D-11cf-96B8-444553540000 twffan="done"><PARAM NAME="_cx" VALUE="10848"><PARAM NAME="_cy" VALUE="1588"><PARAM NAME="FlashVars" VALUE=""><PARAM NAME="Movie" VALUE="http://www.pconline.com.cn/images/200308/ad/wuxian_410.swf"><PARAM NAME="Src" VALUE="http://www.pconline.com.cn/images/200308/ad/wuxian_410.swf"><PARAM NAME="WMode" VALUE="Window"><PARAM NAME="Play" VALUE="-1"><PARAM NAME="Loop" VALUE="-1"><PARAM NAME="Quality" VALUE="High"><PARAM NAME="SAlign" VALUE=""><PARAM NAME="Menu" VALUE="-1"><PARAM NAME="Base" VALUE=""><PARAM NAME="AllowScriptAccess" VALUE="always"><PARAM NAME="Scale" VALUE="ShowAll"><PARAM NAME="DeviceFont" VALUE="0"><PARAM NAME="EmbedMovie" VALUE="0"><PARAM NAME="BGColor" VALUE=""><PARAM NAME="SWRemote" VALUE=""><PARAM NAME="MovieData" VALUE=""><PARAM NAME="SeamlessTabbing" VALUE="1"><PARAM NAME="Profile" VALUE="0"><PARAM NAME="ProfileAddress" VALUE=""><PARAM NAME="ProfilePort" VALUE="0">
<embed src="http://www.pconline.com.cn/images/200308/ad/wuxian_410.swf"  
width=410 height=60 type="application/x-shockwave-flash" 
pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash"></embed></OBJECT></SPAN></DIV>
<P>　　（2）把声明放在合适的位置上<BR><BR>　　在一些场合，通过移动声明到合适的位置所带来的性能提升应该引起我们足够的重视。例如：<BR><BR>bool is_C_Needed(); <BR>void use()<BR>{<BR>C c1;<BR>if (is_C_Needed() == false)<BR>{<BR>return; //c1 was not needed<BR>} <BR>//use c1 here<BR>return; <BR>}<BR><BR>　　上面这段代码中对象c1即使在有可能不使用它的情况下也会被创建，这样我们就会为它付出不必要的花费，有可能你会说一个对象c1能浪费多少时间，但是如果是这种情况呢：C c1[1000];我想就不是说浪费就浪费了。但是我们可以通过移动声明c1的位置来改变这种情况：<BR><BR>void use()<BR>{<BR>if (is_C_Needed() == false)<BR>{<BR>return; //c1 was not needed<BR>} <BR>C c1; //moved from the block's beginning<BR>//use c1 here<BR>return; <BR>}<BR><BR>　　怎么样，<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的性能是不是已经得到很大的改善了呢？因此请仔细分析你的代码，把声明放在合适的位置上，它所带来的好处是你难以想象的。</P>
<P>　（3） 初始化列表<BR><BR>　　我们都知道，初始化列表一般是用来初始化const或者reference数据成员。但是由于他自身的性质，我们可以通过使用初始化列表来实现性能的提升。我们先来看一段<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>：<BR><BR>class Person<BR>{<BR>private:<BR>C c_1;<BR>C c_2;<BR>public:<BR>Person(const C&amp; c1, const C&amp; c2 ): c_1(c1), c_2(c2) {}<BR>};<BR><BR>　　当然构造函数我们也可以这样写：<BR><BR>Person::Person(const C&amp; c1, const C&amp; c2)<BR>{<BR>c_1 = c1; <BR>c_2 = c2; <BR>}<BR><BR>　　那么究竟二者会带来什么样的性能差异呢，要想搞清楚这个问题，我们首先要搞清楚二者是如何执行的，先来看初始化列表：数据成员的声明操作都是在构造函数执行之前就完成了，在构造函数中往往完成的只是赋值操作，然而初始化列表直接是在数据成员声明的时候就进行了初始化，因此它只执行了一次copy constructor。再来看在构造函数中赋值的情况：首先，在构造函数执行前会通过default constructor创建数据成员，然后在构造函数中通过operator =进行赋值。因此它就比初始化列表多进行了一次函数调用。性能差异就出来了。但是请注意，如果你的数据成员都是基本类型的话，那么为了<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的可读性就不要使用初始化列表了，因为编译器对两者产生的汇编代码是相同的。<BR><BR>　　（4）postfix VS prefix 运算符<BR><BR>　　prefix运算符++和—比它的postfix版本效率更高，因为当postfix运算符被使用的时候，会需要一个临时对象来保存改变以前的值。对于基本类型，编译器会消除这一份额外的拷贝，但是对于用户定义类型，这似乎是不可能的。因此请你尽可能使用prefix运算符。<BR>三. 内联函数<BR>　内联函数既能够去除函数调用所带来的效率负担又能够保留一般函数的优点。然而，内联函数并不是万能药，在一些情况下，它甚至能够降低<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的性能。因此在使用的时候应该慎重。<BR><BR>　　1．我们先来看看内联函数给我们带来的好处：从一个用户的角度来看，内联函数看起来和普通函数一样，它可以有参数和返回值，也可以有自己的作用域，然而它却不会引入一般函数调用所带来的负担。另外，它可以比宏更安全更容易调试。<BR><BR>　　当然有一点应该意识到，inline specifier仅仅是对编译器的建议，编译器有权利忽略这个建议。那么编译器是如何决定函数内联与否呢？一般情况下关键性因素包括函数体的大小，是否有局部对象被声明，函数的复杂性等等。<BR><BR>　　2．那么如果一个函数被声明为inline但是却没有被内联将会发生什么呢？理论上，当编译器拒绝内联一个函数的时候，那个函数会像普通函数一样被对待，但是还会出现一些其他的问题。例如下面这段代码：<BR><BR>// filename Time.h<BR>#include<CTIME><BR>#include<IOSTREAM><BR>using namespace std;<BR>class Time<BR>{<BR>public:<BR>inline void Show() { for (int i = 0; i&lt;10; i++) cout&lt;<TIME(0)<<ENDL;}<BR> };<BR><BR>　　因为成员函数Time::Show()包括一个局部变量和一个for循环，所以编译器一般拒绝inline，并且把它当作一个普通的成员函数。但是这个包含类声明的头<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>会被单独的#include进各个独立的编译单元中：<BR><BR>// filename f1.cpp<BR>#include "Time.hj"<BR>void f1()<BR>{<BR>Time t1;<BR>t1.Show();<BR>}<BR><BR>// filename f2.cpp<BR>#include "Time.h"<BR>void f2()<BR>{<BR>Time t2;<BR>t2.Show();<BR>}<BR>　结果编译器为这个<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>生成了两个相同成员函数的拷贝：<BR><BR>void f1();<BR>void f2();<BR>int main()<BR>{<BR>f1(); <BR>f2();<BR>return 0;<BR>}<BR><BR>　　当<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>被链接的时候，linker将会面对两个相同的Time::Show()拷贝，于是函数重定义的连接错误发生。但是老一些的C++实现对付这种情况的办法是通过把一个un-inlined函数当作static来处理。因此每一份函数拷贝仅仅在自己的编译单元中可见，这样链接错误就解决了，但是在<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>中却会留下多份函数拷贝。在这种情况下，<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的性能不但没有提升，反而增加了编译和链接时间以及最终可执行体的大小。<BR><BR>　　但是幸运的是，新的C++标准中关于un-inlined函数的说法已经改变。一个符合标准C++实现应该只生成一份函数拷贝。然而，要想所有的编译器都支持这一点可能还需要很长时间。<BR><BR>　　另外关于内联函数还有两个更令人头疼的问题。第一个问题是该如何进行维护。一个函数开始的时候可能以内联的形式出现，但是随着<A href="http://www.chinaitpower.com/System/index.html" target=_blank>系统</A>的扩展，函数体可能要求添加额外的功能，结果内联函数就变得不太可能，因此需要把inline specifier去除以及把函数体放到一个单独的源<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>中。另一个问题是当内联函数被应用在代码库的时候产生。当内联函数改变的时候，用户必须重新编译他们的代码以反映这种改变。然而对于一个非内联函数，用户仅仅需要重新链接就可以了。<BR><BR>　　这里想要说的是，内联函数并不是一个增强性能的灵丹妙药。只有当函数非常短小的时候它才能得到我们想要的效果，但是如果函数并不是很短而且在很多地方都被调用的话，那么将会使得可执行体的体积增大。最令人烦恼的还是当编译器拒绝内联的时候。在老的实现中，结果很不尽人意，虽然在新的实现中有很大的改善，但是仍然还是不那么完善的。一些编译器能够足够的聪明来指出哪些函数可以内联哪些不能，但是，大多数编译器就不那么聪明了，因此这就需要我们的经验来判断。如果内联函数不能增强行能，就避免使用它。</P><img src ="http://www.cnitblog.com/xianglj/aggbug/4791.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/xianglj/" target="_blank">模式识别技术</a> 2005-11-22 10:18 <a href="http://www.cnitblog.com/xianglj/articles/4791.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>调试方法和技巧</title><link>http://www.cnitblog.com/xianglj/articles/4790.html</link><dc:creator>模式识别技术</dc:creator><author>模式识别技术</author><pubDate>Tue, 22 Nov 2005 02:17:00 GMT</pubDate><guid>http://www.cnitblog.com/xianglj/articles/4790.html</guid><wfw:comment>http://www.cnitblog.com/xianglj/comments/4790.html</wfw:comment><comments>http://www.cnitblog.com/xianglj/articles/4790.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/xianglj/comments/commentRss/4790.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/xianglj/services/trackbacks/4790.html</trackback:ping><description><![CDATA[<P><B>便于调试的代码风格：</B></P>
<OL>
<LI>不用全局变量 
<LI>所有变量都要初始化，成员变量在构造函数中初始化 
<LI>尽量使用const 
<LI>详尽的注释 </LI></OL>
<P><B><A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A>++编译选项：</B></P>
<OL>
<LI>总是使用/W4警告级别 
<LI>在调试版本里总是使用/GZ编译选项，用来发现在Release版本中才有的错误 
<LI>没有警告的编译：保证在编译后没有任何警告，但是在消除警告前要进行仔细检查 </LI></OL>
<P><B>调试方法：</B></P>
<P>1、使用 Assert（原则：尽量简单）<BR>　　assert只在debug下生效，release下不会被编译。<BR><BR>例子： </P><PRE>char* strcpy(char* dest,char* source)
{
	assert(source!=0);
	assert(dest!=0);
	char* returnstring = dest;
	
	while((*dest++ = *source++)!= ‘\0’)
	{
		;
	}
	return returnstring;
}      </PRE>2、防御性的编程<BR><BR>例子： <PRE>char* strcpy(char* dest,char* source)
{
	if(source == 0)
	{
		assert(false);
		reutrn 0;
	}

	if(dest == 0)
	{
		assert(false);
		return 0;
	}
	char* returnstring = dest;
	while((*dest++ = *source++)!= ‘\0’)
	{
		;
	}
	return returnstring;
}      </PRE>3、使用Trace<BR><BR>以下的例子只能在debug中显示，<BR><BR>例子：<BR><BR>a)、TRACE<PRE>CString csTest ＝ “test”；
TRACE（“CString is ％s\n”，csTest）；</PRE>
<P>b)、ATLTRACE<BR><BR>c)、afxDump</P><PRE>CTime time = CTime::GetCurrentTime();
#ifdef _DEBUG
afxDump &lt;&lt; time &lt;&lt; “\n”;
#endif</PRE>
<P>4、用GetLastError来检测返回值，通过得到错误代码来分析错误原因<BR><BR>5、把错误信息记录到<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>中<BR><BR><B>异常处理</B><BR><BR>　　<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>设计时一定要考虑到异常如何处理，当错误发生后，不应简单的报告错误并退出<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>，应当尽可能的想办法恢复到出错前的状态或者让<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>从头开始运行，并且对于某些错误，应该能够容错，即允许错误的存在，但是<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>还是能够正常完成任务。<BR><BR><B>调试技巧</B><BR><BR>1、<A href="http://www.chinaitpower.com/Dev/Programme/VC/index.html" target=_blank>VC</A>++中F5进行调试运行<BR><BR>a)、在output Debug窗口中可以看到用TRACE打印的信息<BR>b)、 Call Stack窗口中能看到<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>的调用堆栈<BR><BR>2、当Debug版本运行时发生崩溃，选择retry进行调试，通过看Call Stack分析出错的位置及原因<BR>3、使用映射<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>调试<BR><BR>a)、创建映射<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>：Project settings中link项，选中Generate mapfile，输出<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>代码地址：/MAPINFO: LINES，得到引出序号：/MAPINFO: EXPORTS。<BR>b)、<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>发布时，应该把所有模块的映射<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>都存档。<BR>c)、查看映射<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>：见” 通过崩溃地址找出源代码的出错行”<A href="http://www.chinaitpower.com/Soft/Tools/File/index.html" target=_blank>文件</A>。<BR><BR>4、可以调试的Release版本<BR><BR>　　Project settings中C++项的Debug Info选择为Program Database，Link项的Debug中选择Debug Info和Microsoft format。<BR><BR>5、查看API的错误码，在watch窗口输入@err可以查看或者@err,hr，其中”,hr”表示错误码的说明。<BR>6、Set Next Statement：该功能可以直接跳转到指定的代码行执行，一般用来测试异常处理的代码。<BR>7、调试内存变量的变化：当内存发生变化时停下来。<BR><BR><B>常见错误</B><BR><BR>1、在函数返回的时候<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>崩溃的原因<BR><BR>a)、写自动变量越界<BR>b)、函数原型不匹配<BR><BR>2、MFC<BR><BR>a)、使用错误的函数原型处理用户定义消息<BR><BR>正确的函数原型为：</P><PRE>afx_msg LRESULT OnMyMessage(WPARAM wParam,LPARAM lParam);</PRE>
<P>3、谨慎使用TerminateThread：使用TerminateThread会造成资源泄漏，不到万不得已，不要使用。<BR><BR>4、使用_beginthreadex，不要使用Create Thread来常见线程。<BR><BR><B>参考资料：</B><BR>《Windows<A href="http://www.chinaitpower.com/Dev/index.html" target=_blank>程序</A>调试》<BR></P><img src ="http://www.cnitblog.com/xianglj/aggbug/4790.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/xianglj/" target="_blank">模式识别技术</a> 2005-11-22 10:17 <a href="http://www.cnitblog.com/xianglj/articles/4790.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>