﻿<?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++博客-Macaulish-文章分类-c/c++</title><link>http://www.cppblog.com/Macaulish/category/6380.html</link><description>用最初的心干永远的事！
qq:396577215
e_mail:fengmin_18@hotmail.com</description><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 00:55:10 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 00:55:10 GMT</pubDate><ttl>60</ttl><item><title>C++中三个修饰符的深层剖析</title><link>http://www.cppblog.com/Macaulish/articles/46215.html</link><dc:creator>fengmin</dc:creator><author>fengmin</author><pubDate>Thu, 03 Apr 2008 14:31:00 GMT</pubDate><guid>http://www.cppblog.com/Macaulish/articles/46215.html</guid><wfw:comment>http://www.cppblog.com/Macaulish/comments/46215.html</wfw:comment><comments>http://www.cppblog.com/Macaulish/articles/46215.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Macaulish/comments/commentRss/46215.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Macaulish/services/trackbacks/46215.html</trackback:ping><description><![CDATA[static 是c++中很常用的修饰符，它被用来控制变量的存储方式和可见性，下面我将从 static 修饰符的产生原因、作用谈起，全面分析static 修饰符的实质。
<p>　　<strong>static 的两大作用: </strong></p>
<p>　　一、控制存储方式： </p>
<p>　　static被引入以告知编译器，将变量存储在程序的静态存储区而非栈上空间。 </p>
<p>　　1、引出原因：函数内部定义的变量，在程序执行到它的定义处时，编译器为它在栈上分配空间，大家知道，函数在栈上分配的空间在此函数执行结束时会释放掉，这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时，如何实现？ </p>
<p>　　最容易想到的方法是定义一个全局的变量，但定义为一个全局变量有许多缺点，最明显的缺点是破坏了此变量的访问范围（使得在此函数中定义的变量，不仅仅受此函数控制）。 </p>
<p>　　2、 解决方案：因此c++ 中引入了static，用它来修饰变量，它能够指示编译器将此变量在程序的静态存储区分配空间保存，这样即实现了目的，又使得此变量的存取范围不变。 </p>
<p>　　二、控制可见性与连接类型 : </p>
<p>　　static还有一个作用，它会把变量的可见范围限制在编译单元中，使它成为一个内部连接，这时，它的反义词为&#8221;extern&#8221;. </p>
<p>　　static作用分析总结：static总是使得变量或对象的存储形式变成静态存储，连接方式变成内部连接，对于局部变量（已经是内部连接了），它仅改变其存储方式；对于全局变量（已经是静态存储了），它仅改变其连接类型。 </p>
<p>　<strong>　类中的static成员： </strong></p>
<p>　　一、出现原因及作用： </p>
<p>　　1、需要在一个类的各个对象间交互，即需要一个数据对象为整个类而非某个对象服务。 </p>
<p>　　2、同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部，对外不可见。 </p>
<p>　　类的static成员满足了上述的要求，因为它具有如下特征：有独立的存储区，属于整个类。 </p>
<p>　　二、注意： </p>
<p>　　1、对于静态的数据成员，连接器会保证它拥有一个单一的外部定义。静态数据成员按定义出现的先后顺序依次初始化，注意静态成员嵌套时，要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。 </p>
<p>　　2、类的静态成员函数是属于整个类而非类的对象，所以它没有this指针，这就导致了它仅能访问类的静态数据和静态成员函数。 </p>
<p>　　const 是c++中常用的类型修饰符，但我在工作中发现，许多人使用它仅仅是想当然尔，这样,有时也会用对，但在某些微妙的场合，可就没那么幸运了，究其实质原由，大多因为没有搞清本源。故在本篇中我将对const进行辨析。溯其本源，究其实质，希望能对大家理解const有所帮助，根据思维的承接关系，分为如下几个部分进行阐述。 </p>
<p>　　<strong>c++中为什么会引入const </strong></p>
<p>　　c++的提出者当初是基于什么样的目的引入（或者说保留）const关键字呢？，这是一个有趣又有益的话题，对理解const很有帮助。 </p>
<p>　　1． 大家知道，c++有一个类型严格的编译系统，这使得c++程序的错误在编译阶段即可发现许多，从而使得出错率大为减少，因此，也成为了c++与c相比，有着突出优点的一个方面。 </p>
<p>　　2． c中很常见的预处理指令 #define variablename variablevalue 可以很方便地进行值替代，这种值替代至少在三个方面优点突出： </p>
<p>　　一是避免了意义模糊的数字出现，使得程序语义流畅清晰，如下例： <br>　　#define user_num_max 107 这样就避免了直接使用107带来的困惑。 </p>
<p>　　二是可以很方便地进行参数的调整与修改，如上例，当人数由107变为201时，进改动此处即可， </p>
<p>　　三是提高了程序的执行效率，由于使用了预编译器进行值替代，并不需要为这些常量分配存储空间，所以执行的效率较高。 </p>
<p>　　鉴于以上的优点，这种预定义指令的使用在程序中随处可见。 </p>
<p>　　3． 说到这里，大家可能会迷惑上述的1点、2点与const有什么关系呢?,好，请接着向下看： </p>
<p>　　预处理语句虽然有以上的许多优点，但它有个比较致命的缺点，即，预处理语句仅仅只是简单值替代，缺乏类型的检测机制。这样预处理语句就不能享受c++严格类型检查的好处，从而可能成为引发一系列错误的隐患。 </p>
<p>　　4．好了，第一阶段结论出来了： </p>
<p>　　结论： const 推出的初始目的，正是为了取代预编译指令，消除它的缺点，同时继承它的优点。 </p>
<p>　　现在它的形式变成了： </p>
<p>const datatype variablename = variablevalue ; </p>
<p>　　<strong>为什么const能很好地取代预定义语句？ </strong></p>
<p>　　const 到底有什么大神通，使它可以振臂一挥取代预定义语句呢？ </p>
<p>　　1． 首先，以const 修饰的常量值，具有不可变性，这是它能取代预定义语句的基础。 </p>
<p>　　2． 第二，很明显，它也同样可以避免意义模糊的数字出现，同样可以很方便地进行参数的调整和修改。 </p>
<p>　　3． 第三，c++的编译器通常不为普通const常量分配存储空间，而是将它们保存在符号表中，这使得它成为一个编译期间的常量，没有了存储与读内存的操作，使得它的效率也很高，同时，这也是它取代预定义语句的重要基础。这里，我要提一下，为什么说这一点是也是它能取代预定义语句的基础，这是因为，编译器不会去读存储的内容，如果编译器为const分配了存储空间，它就不能够成为一个编译期间的常量了。 </p>
<p>　　4． 最后，const定义也像一个普通的变量定义一样，它会由编译器对它进行类型的检测，消除了预定义语句的隐患。 </p>
<p>　　<strong>const 使用情况分类详析 </strong></p>
<p>　　1.const 用于指针的两种情况分析： </p>
<p>　　int const *a; 　file://a可变，*a不可变 <br>　　int *const a; 　file://a不可变，*a可变 </p>
<p>　　分析：const 是一个左结合的类型修饰符，它与其左侧的类型修饰符和为一个类型修饰符，所以，int const 限定 *a,不限定a。int *const 限定a,不限定*a。 </p>
<p>　　2.const 限定函数的传递值参数： </p>
<p>　　void fun(const int var); </p>
<p>　　分析：上述写法限定参数在函数体中不可被改变。由值传递的特点可知，var在函数体中的改变不会影响到函数外部。所以，此限定与函数的使用者无关，仅与函数的编写者有关。 </p>
<p>　　结论：最好在函数的内部进行限定，对外部调用者屏蔽，以免引起困惑。如可改写如下： </p>
<p>　　void fun(int var){ <br>　　const int &amp; varalias = var; </p>
<p>　　varalias .... </p>
<p>　　..... </p>
<p>　　} </p>
<p>　　3.const 限定函数的值型返回值： </p>
<p>　　const int fun1(); </p>
<p>　　const myclass fun2(); </p>
<p>　　分析:上述写法限定函数的返回值不可被更新，当函数返回内部的类型时（如fun1），已经是一个数值，当然不可被赋值更新，所以，此时const无意义，最好去掉，以免困惑。当函数返回自定义的类型时（如fun2），这个类型仍然包含可以被赋值的变量成员，所以，此时有意义。 </p>
<p>　　4. 传递与返回地址： 此种情况最为常见，由地址变量的特点可知，适当使用const，意义昭然。 </p>
<p>　　5.　const 限定类的成员函数： </p>
<p>　　class classname { </p>
<p>　　public: </p>
<p>　　int fun() const; </p>
<p>　　..... </p>
<p>　　} </p>
<p>　　注意：采用此种const 后置的形式是一种规定，亦为了不引起混淆。在此函数的声明中和定义中均要使用const,因为const已经成为类型信息的一部分。 </p>
<p>　　获得能力：可以操作常量对象。 </p>
<p>　　失去能力：不能修改类的数据成员，不能在函数中调用其他不是const的函数。 </p>
<p>　　在本文中，const方面的知识我讲的不多，因为我不想把它变成一本c++的教科书。我只是想详细地阐述它的实质和用处. 我会尽量说的很详细，因为我希望在一种很轻松随意的气氛中说出自己的某些想法，毕竟，编程也是轻松，快乐人生的一部分。有时候，你会惊叹这其中的世界原来是如此的精美。 <br>　　在前面谈了const后，现在再来谈一下inline这个关键字，之所以把这个问题放在这个位置,是因为inline这个关键字的引入原因和const十分相似，下面分为如下几个部分进行阐述。 </p>
<p>　　c++中引入inline关键字的原因: </p>
<p>　　inline 关键字用来定义一个类的内联函数，引入它的主要原因是用它替代c中表达式形式的宏定义。 </p>
<p>　　表达式形式的宏定义一例： </p>
<p>　　　#define expressionname(var1,var2) (var1+var2)*(var1-var2) </p>
<p>　　为什么要取代这种形式呢，且听我道来： </p>
<p>　　1． 首先谈一下在c中使用这种形式宏定义的原因，c语言是一个效率很高的语言，这种宏定义在形式及使用上像一个函数，但它使用预处理器实现，没有了参数压栈，代码生成等一系列的操作,因此，效率很高，这是它在c中被使用的一个主要原因。 </p>
<p>　　2． 这种宏定义在形式上类似于一个函数，但在使用它时，仅仅只是做预处理器符号表中的简单替换，因此它不能进行参数有效性的检测，也就不能享受c++编译器严格类型检查的好处，另外它的返回值也不能被强制转换为可转换的合适的类型，这样，它的使用就存在着一系列的隐患和局限性。 </p>
<p>　　3． 在c++中引入了类及类的访问控制，这样，如果一个操作或者说一个表达式涉及到类的保护成员或私有成员，你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。 </p>
<p>　　4． inline 推出的目的，也正是为了取代这种表达式形式的宏定义，它消除了它的缺点，同时又很好地继承了它的优点。 </p>
<p>　　为什么inline能很好地取代表达式形式的预定义呢？ </p>
<p>　　对应于上面的1-3点，阐述如下： </p>
<p>　　1． inline 定义的类的内联函数，函数的代码被放入符号表中，在使用时直接进行替换，（像宏一样展开），没有了调用的开销，效率也很高。 </p>
<p>　　2． 很明显，类的内联函数也是一个真正的函数，编译器在调用一个内联函数时，会首先检查它的参数的类型，保证调用正确。然后进行一系列的相关检查，就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。 </p>
<p>　　3． inline 可以作为某个类的成员函数，当然就可以在其中使用所在类的保护成员及私有成员。 <br>在何时使用inline函数： </p>
<p>　　首先，你可以使用inline函数完全取代表达式形式的宏定义。 </p>
<p>　　另外要注意，内联函数一般只会用在函数内容非常简单的时候，这是因为，内联函数的代码会在任何调用它的地方展开，如果函数太复杂，代码膨胀带来的恶果很可能会大于效率的提高带来的益处。 内联函数最重要的使用地方是用于类的存取函数。 </p>
<p>　　如何使用类的inline函数： </p>
<p>　　简单提一下inline 的使用吧： </p>
<p>　　1.在类中定义这种函数： </p>
<p>　　class classname{ </p>
<p>　　..... </p>
<p>　　.... </p>
<p>　　getwidth(){return m_lpicwidth;}; // 如果在类中直接定义，可以不使用inline修饰 </p>
<p>　　.... </p>
<p>　　.... </p>
<p>　　} </p>
<p>　　2.在类中声明，在类外定义: </p>
<p>　　class classname{ </p>
<p>　　..... </p>
<p>　　.... </p>
<p>　　getwidth(); // 如果在类中直接定义，可以不使用inline修饰 </p>
<p>　　.... </p>
<p>　　.... </p>
<p>　　} </p>
<p>　　inline getwidth(){ </p>
<p>　　return m_lpicwidth; </p>
<p>　　} </p>
<p>　　在本文中，谈了一种特殊的函数，类的inline函数，它的源起和特点在某种说法上与const很类似，可以与const搭配起来看。</p>
<img src ="http://www.cppblog.com/Macaulish/aggbug/46215.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Macaulish/" target="_blank">fengmin</a> 2008-04-03 22:31 <a href="http://www.cppblog.com/Macaulish/articles/46215.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>浅谈内存泄漏(转载)</title><link>http://www.cppblog.com/Macaulish/articles/46214.html</link><dc:creator>fengmin</dc:creator><author>fengmin</author><pubDate>Thu, 03 Apr 2008 14:28:00 GMT</pubDate><guid>http://www.cppblog.com/Macaulish/articles/46214.html</guid><wfw:comment>http://www.cppblog.com/Macaulish/comments/46214.html</wfw:comment><comments>http://www.cppblog.com/Macaulish/articles/46214.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Macaulish/comments/commentRss/46214.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Macaulish/services/trackbacks/46214.html</trackback:ping><description><![CDATA[对于一个c/c++程序员来说，内存泄漏是一个常见的也是令人头疼的问题。已经有许多技术被研究出来以应对这个问题，比如Smart Pointer，Garbage Collection等。Smart Pointer技术比较成熟，STL中已经包含支持Smart Pointer的class，但是它的使用似乎并不广泛，而且它也不能解决所有的问题；Garbage Collection技术在Java中已经比较成熟，但是在c/c++领域的发展并不顺畅，虽然很早就有人思考在C++中也加入GC的支持。现实世界就是这样的，作为一个c/c++程序员，内存泄漏是你心中永远的痛。不过好在现在有许多工具能够帮助我们验证内存泄漏的存在，找出发生问题的代码。
<p><br><strong>内存泄漏的定义 </strong><br>　　一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的，大小任意的（内存块的大小可以在程序运行期决定），使用完后必须显示释放的内存。应用程序一般使用malloc，realloc，new等函数从堆中分配到一块内存，使用完后，程序必须负责相应的调用free或delete释放该内存块，否则，这块内存就不能被再次使用，我们就说这块内存泄漏了。以下这段小程序演示了堆内存发生泄漏的情形： </p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>void MyFunction(int nSize) <br>{ <br>char* p= new char[nSize]; <br>if( !GetStringFrom( p, nSize ) ){ <br>MessageBox(&#8220;Error&#8221;); <br>return; <br>} <br>&#8230;//using the string pointed by p; <br>delete p; <br>}</td>
        </tr>
    </tbody>
</table>
<p><br>　　当函数GetStringFrom()返回零的时候，指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存，在出口处释放内存，但是c函数可以在任何地方退出，所以一旦有某个出口处没有释放应该释放的内存，就会发生内存泄漏。 <br>广义的说，内存泄漏不仅仅包含堆内存的泄漏，还包含系统资源的泄漏(resource leak)，比如核心态HANDLE，GDI Object，SOCKET，Interface等，从根本上说这些由操作系统分配的对象也消耗内存，如果这些对象发生泄漏最终也会导致内存的泄漏。而且，某些对象消耗的是核心态内存，这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下，系统资源的泄漏比堆内存的泄漏更为严重。 <br>　　GDI Object的泄漏是一种常见的资源泄漏： </p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>void CMyView::OnPaint( CDC* pDC ) <br>{ <br>CBitmap bmp; <br>CBitmap* pOldBmp; <br>bmp.LoadBitmap(IDB_MYBMP); <br>pOldBmp = pDC-&gt;SelectObject( &amp;bmp ); <br>&#8230; <br>if( Something() ){ <br>return; <br>} <br>pDC-&gt;SelectObject( pOldBmp ); <br>return; <br>}</td>
        </tr>
    </tbody>
</table>
<p>　　当函数Something()返回非零的时候，程序在退出前没有把pOldBmp选回pDC中，这会导致pOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长时间的运行，可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来，因为Win9x的GDI堆比Win2k或NT的要小很多。 <br>内存泄漏的发生方式： <br>　　以发生的方式来分类，内存泄漏可以分为4类： <br>　　1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到，每次被执行的时候都会导致一块内存泄漏。比如例二，如果Something()函数一直返回True，那么pOldBmp指向的HBITMAP对象总是发生泄漏。 <br>　　2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二，如果Something()函数只有在特定环境下才返回True，那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境，偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。 <br>　　3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次，或者由于算法上的缺陷，导致总会有一块仅且一块内存发生泄漏。比如，在类的构造函数中分配内存，在析构函数中却没有释放该内存，但是因为这个类是一个Singleton，所以内存泄漏只会发生一次。另一个例子： </p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>char* g_lpszFileName = NULL; <br>void SetFileName( const char* lpcszFileName ) <br>{ <br>if( g_lpszFileName ){ <br>free( g_lpszFileName ); <br>} <br>g_lpszFileName = strdup( lpcszFileName ); <br>}</td>
        </tr>
    </tbody>
</table>
<p><br>　　如果程序在结束的时候没有释放g_lpszFileName指向的字符串，那么，即使多次调用SetFileName()，总会有一块内存，而且仅有一块内存发生泄漏。 <br>　　4. 隐式内存泄漏。程序在运行过程中不停的分配内存，但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏，因为最终程序释放了所有申请的内存。但是对于一个服务器程序，需要运行几天，几周甚至几个月，不及时释放内存也可能导致最终耗尽系统的所有内存。所以，我们称这类内存泄漏为隐式内存泄漏。举一个例子： </p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>class Connection <br>{ <br>public: <br>Connection( SOCKET s); <br>~Connection(); <br>&#8230; <br>private: <br>SOCKET _socket; <br>&#8230; <br>}; <br>class ConnectionManager <br>{ <br>public: <br>ConnectionManager(){ <br>} <br>~ConnectionManager(){ <br>list&lt;Connection&gt;::iterator it; <br>for( it = _connlist.begin(); it != _connlist.end(); ++it ){ <br>delete （*it）; <br>} <br>_connlist.clear(); <br>} <br>void OnClientConnected( SOCKET s ){ <br>Connection* p = new Connection(s); <br>_connlist.push_back(p); <br>} <br>void OnClientDisconnected( Connection* pconn ){ <br>_connlist.remove( pconn ); <br>delete pconn; <br>} <br>private: <br>list&lt;Connection*&gt; _connlist; <br>};</td>
        </tr>
    </tbody>
</table>
<p><br>　　假设在Client从Server端断开后，Server并没有呼叫OnClientDisconnected()函数，那么代表那次连接的Connection对象就不会被及时的删除（在Server程序退出的时候，所有Connection对象会在ConnectionManager的析构函数里被删除）。当不断的有连接建立、断开时隐式内存泄漏就发生了。 <br>　　从用户使用程序的角度来看，内存泄漏本身不会产生什么危害，作为一般的用户，根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积，这会最终消耗尽系统所有的内存。从这个角度来说，一次性内存泄漏并没有什么危害，因为它不会堆积，而隐式内存泄漏危害性则非常大，因为较之于常发性和偶发性内存泄漏它更难被检测到。</p>
<strong>检测内存泄漏：</strong> <br><br>　　检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数，我们就能跟踪每一块内存的生命周期，比如，每当成功的分配一块内存后，就把它的指针加入一个全局的list中；每当释放一块内存，再把它的指针从list中删除。这样，当程序结束的时候，list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理，详细的算法可以参见Steve Maguire的&lt;&lt;Writing Solid Code&gt;&gt;。 <br>　　如果要检测堆内存的泄漏，那么需要截获住malloc/realloc/free和new/delete就可以了（其实new/delete最终也是用malloc/free的，所以只要截获前面一组即可）。对于其他的泄漏，可以采用类似的方法，截获住相应的分配和释放函数。比如，要检测BSTR的泄漏，就需要截获SysAllocString/SysFreeString；要检测HMENU的泄漏，就需要截获CreateMenu/ DestroyMenu。（有的资源的分配函数有多个，释放函数只有一个，比如，SysAllocStringLen也可以用来分配BSTR，这时就需要截获多个分配函数） <br>　　在Windows平台下，检测内存泄漏的工具常用的一般有三种，MS C-Runtime Library内建的检测功能；外挂式的检测工具，诸如，Purify，BoundsChecker等；利用Windows NT自带的Performance Monitor。这三种工具各有优缺点，MS C-Runtime Library虽然功能上较之外挂式的工具要弱，但是它是免费的；Performance Monitor虽然无法标示出发生问题的代码，但是它能检测出隐式的内存泄漏的存在，这是其他两类工具无能为力的地方。 <br>　　以下我们详细讨论这三种检测工具：
<p><br><strong>VC下内存泄漏的检测方法 </strong><br><br>　　用MFC开发的应用程序，在DEBUG版模式下编译后，都会自动加入内存泄漏的检测代码。在程序结束后，如果发生了内存泄漏，在Debug窗口中会显示出所有发生泄漏的内存块的信息，以下两行显示了一块被泄漏的内存块的信息： <br>E:\TestMemLeak\TestDlg.cpp(70) : {59} normal block at 0x00881710, 200 bytes long. <br>Data: &lt;abcdefghijklmnop&gt; 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 <br>　　第一行显示该内存块由TestDlg.cpp文件，第70行代码分配，地址在0x00881710，大小为200字节，{59}是指调用内存分配函数的Request Order，关于它的详细信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行显示该内存块前16个字节的内容，尖括号内是以ASCII方式显示，接着的是以16进制方式显示。 <br>　　一般大家都误以为这些内存泄漏的检测功能是由MFC提供的，其实不然。MFC只是封装和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。MS C-Runtime Library在实现malloc/free，strdup等函数时已经内建了内存泄漏的检测功能。 <br>　　注意观察一下由MFC Application Wizard生成的项目，在每一个cpp文件的头部都有这样一段宏定义： <br>#ifdef _DEBUG <br>#define new DEBUG_NEW <br>#undef THIS_FILE <br>static char THIS_FILE[] = __FILE__; <br>#endif <br>　　有了这样的定义，在编译DEBUG版时，出现在这个cpp文件中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢？DEBUG_NEW也是一个宏，以下摘自afx.h，1632行 <br>#define DEBUG_NEW new(THIS_FILE, __LINE__) <br>所以如果有这样一行代码： <br>char* p = new char[200]; <br>经过宏替换就变成了： <br>char* p = new( THIS_FILE, __LINE__)char[200]; <br>根据C++的标准，对于以上的new的使用方法，编译器会去找这样定义的operator new： <br>void* operator new(size_t, LPCSTR, int) <br>我们在afxmem.cpp 63行找到了一个这样的operator new 的实现</p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine) <br>{ <br>return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine); <br>} <br>void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine) <br>{ <br>&#8230; <br>pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine); <br>if (pResult != NULL) <br>return pResult; <br>&#8230; <br>}</td>
        </tr>
    </tbody>
</table>
<p><br>　　第二个operator new函数比较长，为了简单期间，我只摘录了部分。很显然最后的内存分配还是通过_malloc_dbg函数实现的，这个函数属于MS C-Runtime Library 的Debug Function。这个函数不但要求传入内存的大小，另外还有文件名和行号两个参数。文件名和行号就是用来记录此次分配是由哪一段代码造成的。如果这块内存在程序结束之前没有被释放，那么这些信息就会输出到Debug窗口里。 <br>　　这里顺便提一下THIS_FILE，__FILE和__LINE__。__FILE__和__LINE__都是编译器定义的宏。当碰到__FILE__时，编译器会把__FILE__替换成一个字符串，这个字符串就是当前在编译的文件的路径名。当碰到__LINE__时，编译器会把__LINE__替换成一个数字，这个数字就是当前这行代码的行号。在DEBUG_NEW的定义中没有直接使用__FILE__，而是用了THIS_FILE，其目的是为了减小目标文件的大小。假设在某个cpp文件中有100处使用了new，如果直接使用__FILE__，那编译器会产生100个常量字符串，这100个字符串都是这个cpp文件的路径名，显然十分冗余。如果使用THIS_FILE，编译器只会产生一个常量字符串，那100处new的调用使用的都是指向常量字符串的指针。 <br>　　再次观察一下由MFC Application Wizard生成的项目，我们会发现在cpp文件中只对new做了映射，如果你在程序中直接使用malloc函数分配内存，调用malloc的文件名和行号是不会被记录下来的。如果这块内存发生了泄漏，MS C-Runtime Library仍然能检测到，但是当输出这块内存块的信息，不会包含分配它的的文件名和行号。 <br>要在非MFC程序中打开内存泄漏的检测功能非常容易，你只要在程序的入口处加入以下几行代码： </p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ); <br>tmpFlag |= _CRTDBG_LEAK_CHECK_DF; <br>_CrtSetDbgFlag( tmpFlag );</td>
        </tr>
    </tbody>
</table>
<p><br>　　这样，在程序结束的时候，也就是winmain，main或dllmain函数返回之后，如果还有内存块没有释放，它们的信息会被打印到Debug窗口里。 <br>如果你试着创建了一个非MFC应用程序，而且在程序的入口处加入了以上代码，并且故意在程序中不释放某些内存块，你会在Debug窗口里看到以下的信息： <br>{47} normal block at 0x00C91C90, 200 bytes long. <br>Data: &lt; &gt; 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F <br>　　内存泄漏的确检测到了，但是和上面MFC程序的例子相比，缺少了文件名和行号。对于一个比较大的程序，没有这些信息，解决问题将变得十分困难。 <br>　　为了能够知道泄漏的内存块是在哪里分配的，你需要实现类似MFC的映射功能，把new，maolloc等函数映射到_malloc_dbg函数上。这里我不再赘述，你可以参考MFC的源代码。 <br>　　由于Debug Function实现在MS C-RuntimeLibrary中，所以它只能检测到堆内存的泄漏，而且只限于malloc，realloc或strdup等分配的内存，而那些系统资源，比如HANDLE，GDI Object，或是不通过C-Runtime Library分配的内存，比如VARIANT，BSTR的泄漏，它是无法检测到的，这是这种检测法的一个重大的局限性。另外，为了能记录内存块是在哪里分配的，源代码必须相应的配合，这在调试一些老的程序非常麻烦，毕竟修改源代码不是一件省心的事，这是这种检测法的另一个局限性。 <br>　　对于开发一个大型的程序，MS C-Runtime Library提供的检测功能是远远不够的。接下来我们就看看外挂式的检测工具。我用的比较多的是BoundsChecker，一则因为它的功能比较全面，更重要的是它的稳定性。这类工具如果不稳定，反而会忙里添乱。到底是出自鼎鼎大名的NuMega，我用下来基本上没有什么大问题。</p>
<strong>使用BoundsChecker检测内存泄漏：<br><br></strong>　　BoundsChecker采用一种被称为Code Injection的技术，来截获对分配内存和释放内存的函数的调用。简单地说，当你的程序开始运行时，BoundsChecker的DLL被自动载入进程的地址空间（这可以通过system-level的Hook实现），然后它会修改进程中对内存分配和释放的函数调用，让这些调用首先转入它的代码，然后再执行原来的代码。BoundsChecker在做这些动作的时，无须修改被调试程序的源代码或工程配置文件，这使得使用它非常的简便、直接。 <br>　　这里我们以malloc函数为例，截获其他的函数方法与此类似。 <br>　　需要被截获的函数可能在DLL中，也可能在程序的代码里。比如，如果静态连结C-Runtime Library，那么malloc函数的代码会被连结到程序里。为了截获住对这类函数的调用，BoundsChecker会动态修改这些函数的指令。 <br>　　以下两段汇编代码，一段没有BoundsChecker介入，另一段则有BoundsChecker的介入：
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>126: _CRTIMP void * __cdecl malloc ( <br>127: size_t nSize <br>128: ) <br>129: { <br>00403C10 push ebp <br>00403C11 mov ebp,esp <br>130: return _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0); <br>00403C13 push 0 <br>00403C15 push 0 <br>00403C17 push 1 <br>00403C19 mov eax,[__newmode (0042376c)] <br>00403C1E push eax <br>00403C1F mov ecx,dword ptr [nSize] <br>00403C22 push ecx <br>00403C23 call _nh_malloc_dbg (00403c80) <br>00403C28 add esp,14h <br>131: }</td>
        </tr>
    </tbody>
</table>
<p><br>　　以下这一段代码有BoundsChecker介入：</p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>126: _CRTIMP void * __cdecl malloc ( <br>127: size_t nSize <br>128: ) <br>129: { <br>00403C10 jmp 01F41EC8 <br>00403C15 push 0 <br>00403C17 push 1 <br>00403C19 mov eax,[__newmode (0042376c)] <br>00403C1E push eax <br>00403C1F mov ecx,dword ptr [nSize] <br>00403C22 push ecx <br>00403C23 call _nh_malloc_dbg (00403c80) <br>00403C28 add esp,14h <br>131: }</td>
        </tr>
    </tbody>
</table>
<p>　　当BoundsChecker介入后，函数malloc的前三条汇编指令被替换成一条jmp指令，原来的三条指令被搬到地址01F41EC8处了。当程序进入malloc后先jmp到01F41EC8，执行原来的三条指令，然后就是BoundsChecker的天下了。大致上它会先记录函数的返回地址（函数的返回地址在stack上，所以很容易修改），然后把返回地址指向属于BoundsChecker的代码，接着跳到malloc函数原来的指令，也就是在00403c15的地方。当malloc函数结束的时候，由于返回地址被修改，它会返回到BoundsChecker的代码中，此时BoundsChecker会记录由malloc分配的内存的指针，然后再跳转到到原来的返回地址去。 <br>　　如果内存分配/释放函数在DLL中，BoundsChecker则采用另一种方法来截获对这些函数的调用。BoundsChecker通过修改程序的DLL Import Table让table中的函数地址指向自己的地址，以达到截获的目的。关于如何拦截Windows的系统函数，《程序员》杂志2002年8期，《API钩子揭密（下）》，对修改导入地址表做了概要的描述。我就不再赘述。 <br>　　截获住这些分配和释放函数，BoundsChecker就能记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关，也就是说当BoundsChecker检测到内存泄漏，它如何报告这块内存块是哪段代码分配的。答案是调试信息（Debug Information）。当我们编译一个Debug版的程序时，编译器会把源代码和二进制代码之间的对应关系记录下来，放到一个单独的文件里(.pdb)或者直接连结进目标程序中。有了这些信息，调试器才能完成断点设置，单步执行，查看变量等功能。BoundsChecker支持多种调试信息格式，它通过直接读取调试信息就能得到分配某块内存的源代码在哪个文件，哪一行上。使用Code Injection和Debug Information，使BoundsChecker不但能记录呼叫分配函数的源代码的位置，而且还能记录分配时的Call Stack，以及Call Stack上的函数的源代码位置。这在使用像MFC这样的类库时非常有用，以下我用一个例子来说明： </p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>void ShowXItemMenu() <br>{ <br>&#8230; <br>CMenu menu; <br>menu.CreatePopupMenu(); <br>//add menu items. <br>menu.TrackPropupMenu(); <br>&#8230; <br>} <br>void ShowYItemMenu( ) <br>{ <br>&#8230; <br>CMenu menu; <br>menu.CreatePopupMenu(); <br>//add menu items. <br>menu.TrackPropupMenu(); <br>menu.Detach();//this will cause HMENU leak <br>&#8230; <br>} <br>BOOL CMenu::CreatePopupMenu() <br>{ <br>&#8230; <br>hMenu = CreatePopupMenu(); <br>&#8230; <br>}</td>
        </tr>
    </tbody>
</table>
<p><br>　　当调用ShowYItemMenu()时，我们故意造成HMENU的泄漏。但是，对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假设的你的程序有许多地方使用了CMenu的CreatePopupMenu()函数，如果只是告诉你泄漏是由CMenu::CreatePopupMenu()造成的，你依然无法确认问题的根结到底在哪里，在ShowXItemMenu()中还是在ShowYItemMenu()中，或者还有其它的地方也使用了CreatePopupMenu()？有了Call Stack的信息，问题就容易了。BoundsChecker会如下报告泄漏的HMENU的信息： </p>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <tbody>
        <tr bgColor=#ffffff>
            <td class=halfrow align=left bgColor=#e6e6e6>
            <p>&#160;</p>
            <p>Function <br>File <br>Line </p>
            <p>CMenu::CreatePopupMenu <br>E:\8168\vc98\mfc\mfc\include\afxwin1.inl <br>1009 </p>
            <p>ShowYItemMenu <br>E:\testmemleak\mytest.cpp <br>100</p>
            </td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p>　　这里省略了其他的函数调用 <br></p>
<p>　　如此，我们很容易找到发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编程时，大部分的API调用都被封装在类库的class里，有了Call Stack信息，我们就可以非常容易的追踪到真正发生泄漏的代码。 <br>　　记录Call Stack信息会使程序的运行变得非常慢，因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开关： <br>1. 打开菜单：BoundsChecker|Setting&#8230; <br>2. 在Error Detection页中，在Error Detection Scheme的List中选择Custom <br>3. 在Category的Combox中选择Pointer and leak error check <br>4. 钩上Report Call Stack复选框 <br>5. 点击Ok <br>　　基于Code Injection，BoundsChecker还提供了API Parameter的校验功能，memory over run等功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题，所以不在此详述了。 <br>　　尽管BoundsChecker的功能如此强大，但是面对隐式内存泄漏仍然显得苍白无力。所以接下来我们看看如何用Performance Monitor检测内存泄漏。 </p>
<p><br><strong>使用Performance Monitor检测内存泄漏</strong> <br><br>　　NT的内核在设计过程中已经加入了系统监视功能，比如CPU的使用率，内存的使用情况，I/O操作的频繁度等都作为一个个Counter，应用程序可以通过读取这些Counter了解整个系统的或者某个进程的运行状况。Performance Monitor就是这样一个应用程序。 <br>　　为了检测内存泄漏，我们一般可以监视Process对象的Handle Count，Virutal Bytes 和Working Set三个Counter。Handle Count记录了进程当前打开的HANDLE的个数，监视这个Counter有助于我们发现程序是否有Handle泄漏；Virtual Bytes记录了该进程当前在虚地址空间上使用的虚拟内存的大小，NT的内存分配采用了两步走的方法，首先，在虚地址空间上保留一段空间，这时操作系统并没有分配物理内存，只是保留了一段地址。然后，再提交这段空间，这时操作系统才会分配物理内存。所以，Virtual Bytes一般总大于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系统底层的问题; Working Set记录了操作系统为进程已提交的内存的总量，这个值和程序申请的内存总量存在密切的关系，如果程序存在内存的泄漏这个值会持续增加，但是Virtual Bytes却是跳跃式增加的。 <br>　　监视这些Counter可以让我们了解进程使用内存的情况，如果发生了泄漏，即使是隐式内存泄漏，这些Counter的值也会持续增加。但是，我们知道有问题却不知道哪里有问题，所以一般使用Performance Monitor来验证是否有内存泄漏，而使用BoundsChecker来找到和解决问题。 <br>　　当Performance Monitor显示有内存泄漏，而BoundsChecker却无法检测到，这时有两种可能：第一种，发生了偶发性内存泄漏。这时你要确保使用Performance Monitor和使用BoundsChecker时，程序的运行环境和操作方法是一致的。第二种，发生了隐式的内存泄漏。这时你要重新审查程序的设计，然后仔细研究Performance Monitor记录的Counter的值的变化图，分析其中的变化和程序运行逻辑的关系，找到一些可能的原因。这是一个痛苦的过程，充满了假设、猜想、验证、失败，但这也是一个积累经验的绝好机会。</p>
<img src ="http://www.cppblog.com/Macaulish/aggbug/46214.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Macaulish/" target="_blank">fengmin</a> 2008-04-03 22:28 <a href="http://www.cppblog.com/Macaulish/articles/46214.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>