??xml version="1.0" encoding="utf-8" standalone="yes"?>久久最新精品国产,久久天天躁狠狠躁夜夜96流白浆,亚洲国产高清精品线久久http://www.shnenglu.com/Macaulish/category/6380.html用最初的心干永远的事Q? qq:396577215 e_mail:fengmin_18@hotmail.comzh-cnTue, 20 May 2008 00:55:10 GMTTue, 20 May 2008 00:55:10 GMT60C++中三个修饰符的深层剖?/title><link>http://www.shnenglu.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.shnenglu.com/Macaulish/articles/46215.html</guid><wfw:comment>http://www.shnenglu.com/Macaulish/comments/46215.html</wfw:comment><comments>http://www.shnenglu.com/Macaulish/articles/46215.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/Macaulish/comments/commentRss/46215.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/Macaulish/services/trackbacks/46215.html</trackback:ping><description><![CDATA[static 是c++中很常用的修饰符Q它被用来控制变量的存储方式和可见性,下面我将?static 修饰W的产生原因、作用谈P全面分析static 修饰W的实质? <p>  <strong>static 的两大作? </strong></p> <p>  一、控制存储方式: </p> <p>  static被引入以告知~译器,变量存储在E序的静态存储区而非栈上I间?</p> <p>  1、引出原因:函数内部定义的变量,在程序执行到它的定义处时Q编译器为它在栈上分配空_大家知道Q函数在栈上分配的空间在此函数执行结束时会释放掉Q这样就产生了一个问? 如果惛_函数中此变量的g存至下一ơ调用时Q如何实玎ͼ </p> <p>  最Ҏ惛_的方法是定义一个全局的变量,但定义ؓ一个全局变量有许多缺点,最明显的缺Ҏ破坏了此变量的访问范_使得在此函数中定义的变量Q不仅仅受此函数控制Q?</p> <p>  2?解决ҎQ因此c++ 中引入了staticQ用它来修饰变量Q它能够指示~译器将此变量在E序的静态存储区分配I间保存Q这样即实现了目的,又得此变量的存取范围不变?</p> <p>  二、控制可见性与q接cd : </p> <p>  staticq有一个作用,它会把变量的可见范围限制在编译单元中Q它成Z个内部连接,q时Q它的反义词?#8221;extern”. </p> <p>  static作用分析ȝQstaticL使得变量或对象的存储形式变成静态存储,q接方式变成内部q接Q对于局部变量(已经是内部连接了Q,它仅改变其存储方式;对于全局变量Q已l是静态存储了Q,它仅改变其连接类型?</p> <p> <strong> cM的static成员Q?</strong></p> <p>  一、出现原因及作用Q?</p> <p>  1、需要在一个类的各个对象间交互Q即需要一个数据对象ؓ整个c而非某个对象服务?</p> <p>  2、同时又力求不破坏类的封装?卌求此成员隐藏在类的内部,对外不可见?</p> <p>  cȝstatic成员满了上q的要求Q因为它h如下特征Q有独立的存储区Q属于整个类?</p> <p>  二、注意: </p> <p>  1、对于静态的数据成员Q连接器会保证它拥有一个单一的外部定义。静态数据成员按定义出现的先后顺序依ơ初始化Q注意静态成员嵌套时Q要保证所嵌套的成员已l初始化了。消除时的顺序是初始化的反顺序?</p> <p>  2、类的静态成员函数是属于整个c而非cȝ对象Q所以它没有this指针Q这导致了它仅能访问类的静态数据和静态成员函数?</p> <p>  const 是c++中常用的cd修饰W,但我在工作中发现Q许多h使用它仅仅是惛_然尔Q这?有时也会用对Q但在某些微妙的场合Q可没那么q运了,I其实质原由Q大多因为没有搞清本源。故在本中我将对constq行辨析。溯其本源,I其实质Q希望能对大家理解const有所帮助Q根据思维的承接关p,分ؓ如下几个部分q行阐述?</p> <p>  <strong>c++中ؓ什么会引入const </strong></p> <p>  c++的提当初是Z什么样的目的引入(或者说保留Qconst关键字呢Q,q是一个有又有益的话题,对理解const很有帮助?</p> <p>  1Q?大家知道Qc++有一个类型严格的~译pȝQ这使得c++E序的错误在~译阶段卛_发现许多Q从而得出错率大ؓ减少Q因此,也成Zc++与c相比Q有着H出优点的一个方面?</p> <p>  2Q?c中很常见的预处理指o #define variablename variablevalue 可以很方便地q行值替代,q种值替代至在三个斚w优点H出Q?</p> <p>  一是避免了意义模糊的数字出玎ͼ使得E序语义畅清晰Q如下例Q?<br>  #define user_num_max 107 q样避免了直接使用107带来的困惑?</p> <p>  二是可以很方便地q行参数的调整与修改Q如上例Q当人数?07变ؓ201Ӟq改动此处即可, </p> <p>  三是提高了程序的执行效率Q由于用了预编译器q行值替代,q不需要ؓq些帔R分配存储I间Q所以执行的效率较高?</p> <p>  鉴于以上的优点,q种预定义指令的使用在程序中随处可见?</p> <p>  3Q?说到q里Q大家可能会qh上述?炏V?点与const有什么关pd?,好,h着向下看: </p> <p>  预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,卻I预处理语句仅仅只是简单值替代,~Zcd的检机制。这样预处理语句׃能n受c++严格cd查的好处Q从而可能成为引发一pd错误的隐患?</p> <p>  4Q好了,W一阶段l论出来了: </p> <p>  l论Q?const 推出的初始目的,正是Z取代预编译指令,消除它的~点Q同时承它的优炏V?</p> <p>  现在它的形式变成了: </p> <p>const datatype variablename = variablevalue ; </p> <p>  <strong>Z么const能很好地取代预定义语句? </strong></p> <p>  const 到底有什么大通,使它可以振臂一挥取代预定义语句呢? </p> <p>  1Q?首先Q以const 修饰的常量|h不可变性,q是它能取代预定义语句的基础?</p> <p>  2Q?W二Q很明显Q它也同样可以避免意义模p的数字出现Q同样可以很方便地进行参数的调整和修攏V?</p> <p>  3Q?W三Qc++的编译器通常不ؓ普通const帔R分配存储I间Q而是它们保存在W号表中Q这使得它成Z个编译期间的帔RQ没有了存储与读内存的操作,使得它的效率也很高,同时Q这也是它取代预定义语句的重要基。这里,我要提一下,Z么说q一Ҏ也是它能取代预定义语句的基础Q这是因为,~译器不会去d储的内容Q如果编译器为const分配了存储空_它就不能够成Z个编译期间的帔R了?</p> <p>  4Q?最后,const定义也像一个普通的变量定义一P它会q译器对它q行cd的检,消除了预定义语句的隐患?</p> <p>  <strong>const 使用情况分类详析 </strong></p> <p>  1.const 用于指针的两U情况分析: </p> <p>  int const *a;  file://a可变Q?a不可?<br>  int *const a;  file://a不可变,*a可变 </p> <p>  分析Qconst 是一个左l合的类型修饰符Q它与其左侧的类型修饰符和ؓ一个类型修饰符Q所以,int const 限定 *a,不限定a。int *const 限定a,不限?a?</p> <p>  2.const 限定函数的传递值参敎ͼ </p> <p>  void fun(const int var); </p> <p>  分析Q上q写法限定参数在函数体中不可被改变。由g递的特点可知Qvar在函C中的改变不会影响到函数外部。所以,此限定与函数的用者无养I仅与函数的编写者有兟?</p> <p>  l论Q最好在函数的内部进行限定,对外部调用者屏蔽,以免引v困惑。如可改写如下: </p> <p>  void fun(int var){ <br>  const int & varalias = var; </p> <p>  varalias .... </p> <p>  ..... </p> <p>  } </p> <p>  3.const 限定函数的值型q回| </p> <p>  const int fun1(); </p> <p>  const myclass fun2(); </p> <p>  分析:上述写法限定函数的返回g可被更新Q当函数q回内部的类型时Q如fun1Q,已经是一个数|当然不可被赋值更斎ͼ所以,此时const无意义,最好去掉,以免困惑。当函数q回自定义的cdӞ如fun2Q,q个cd仍然包含可以被赋值的变量成员Q所以,此时有意义?</p> <p>  4. 传递与q回地址Q?此种情况最为常见,由地址变量的特点可知,适当使用constQ意义昭然?</p> <p>  5. const 限定cȝ成员函数Q?</p> <p>  class classname { </p> <p>  public: </p> <p>  int fun() const; </p> <p>  ..... </p> <p>  } </p> <p>  注意Q采用此Uconst 后置的Ş式是一U规定,亦ؓ了不引vh。在此函数的声明中和定义中均要用const,因ؓconst已经成ؓcd信息的一部分?</p> <p>  获得能力Q可以操作常量对象?</p> <p>  失去能力Q不能修改类的数据成员,不能在函C调用其他不是const的函数?</p> <p>  在本文中Qconst斚w的知识我讲的不多Q因为我不想把它变成一本c++的教U书。我只是惌l地阐述它的实质和用? 我会量说的很详l,因ؓ我希望在一U很L随意的气氛中说出自己的某些想法,毕竟Q编E也是轻松,快乐人生的一部分。有时候,你会惊叹q其中的世界原来是如此的_?<br>  在前面谈了const后,现在再来谈一下inlineq个关键字,之所以把q个问题攑֜q个位置,是因为inlineq个关键字的引入原因和const十分怼Q下面分为如下几个部分进行阐q?</p> <p>  c++中引入inline关键字的原因: </p> <p>  inline 关键字用来定义一个类的内联函敎ͼ引入它的主要原因是用它替代c中表辑ּ形式的宏定义?</p> <p>  表达式Ş式的宏定义一例: </p> <p>   #define expressionname(var1,var2) (var1+var2)*(var1-var2) </p> <p>  Z么要取代q种形式呢,且听我道来: </p> <p>  1Q?首先谈一下在c中用这UŞ式宏定义的原因,c语言是一个效率很高的语言Q这U宏定义在Ş式及使用上像一个函敎ͼ但它使用预处理器实现Q没有了参数压栈Q代码生成等一pd的操?因此Q效率很高,q是它在c中被使用的一个主要原因?</p> <p>  2Q?q种宏定义在形式上类g一个函敎ͼ但在使用它时Q仅仅只是做预处理器W号表中的简单替换,因此它不能进行参数有效性的,也就不能享受c++~译器严格类型检查的好处Q另外它的返回g不能被强制{换ؓ可{换的合适的cdQ这P它的使用存在着一pd的隐患和局限性?</p> <p>  3Q?在c++中引入了cdcȝ讉K控制Q这P如果一个操作或者说一个表辑ּ涉及到类的保护成员或U有成员Q你׃可能使用q种宏定义来实现(因ؓ无法this指针攑֜合适的位置)?</p> <p>  4Q?inline 推出的目的,也正是ؓ了取代这U表辑ּ形式的宏定义Q它消除了它的缺点,同时又很好地l承了它的优炏V?</p> <p>  Z么inline能很好地取代表达式Ş式的预定义呢Q?</p> <p>  对应于上面的1-3点,阐述如下Q?</p> <p>  1Q?inline 定义的类的内联函敎ͼ函数的代码被攑օW号表中Q在使用时直接进行替换,Q像宏一样展开Q,没有了调用的开销Q效率也很高?</p> <p>  2Q?很明显,cȝ内联函数也是一个真正的函数Q编译器在调用一个内联函数时Q会首先查它的参数的cdQ保证调用正。然后进行一pd的相x查,像对待M一个真正的函数一栗这样就消除了它的隐患和局限性?</p> <p>  3Q?inline 可以作ؓ某个cȝ成员函数Q当然就可以在其中用所在类的保护成员及U有成员?<br>在何时用inline函数Q?</p> <p>  首先Q你可以使用inline函数完全取代表达式Ş式的宏定义?</p> <p>  另外要注意,内联函数一般只会用在函数内定w常简单的时候,q是因ؓQ内联函数的代码会在M调用它的地方展开Q如果函数太复杂Q代码膨胀带来的恶果很可能会大于效率的提高带来的益处?内联函数最重要的用地Ҏ用于cȝ存取函数?</p> <p>  如何使用cȝinline函数Q?</p> <p>  单提一下inline 的用吧Q?</p> <p>  1.在类中定义这U函敎ͼ </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>  在本文中Q谈了一U特D的函数Q类的inline函数Q它的源起和特点在某U说法上与const很类|可以与const搭配h看?/p> <img src ="http://www.shnenglu.com/Macaulish/aggbug/46215.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/Macaulish/" target="_blank">fengmin</a> 2008-04-03 22:31 <a href="http://www.shnenglu.com/Macaulish/articles/46215.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>谈内存泄漏(转蝲)http://www.shnenglu.com/Macaulish/articles/46214.htmlfengminfengminThu, 03 Apr 2008 14:28:00 GMThttp://www.shnenglu.com/Macaulish/articles/46214.htmlhttp://www.shnenglu.com/Macaulish/comments/46214.htmlhttp://www.shnenglu.com/Macaulish/articles/46214.html#Feedback0http://www.shnenglu.com/Macaulish/comments/commentRss/46214.htmlhttp://www.shnenglu.com/Macaulish/services/trackbacks/46214.html
内存泄漏的定?
  一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指E序从堆中分配的Q大Q意的Q内存块的大可以在E序q行期决定)Q用完后必LC释攄内存。应用程序一般用mallocQreallocQnew{函C堆中分配C块内存,使用完后Q程序必负责相应的调用free或delete释放该内存块Q否则,q块内存׃能被再次使用Q我们就说这块内存泄漏了。以下这D小E序演示了堆内存发生泄漏的情形:

void MyFunction(int nSize)
{
char* p= new char[nSize];
if( !GetStringFrom( p, nSize ) ){
MessageBox(“Error”);
return;
}
…//using the string pointed by p;
delete p;
}


  当函数GetStringFrom()q回零的时候,指针p指向的内存就不会被释放。这是一U常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存Q但是c函数可以在Q何地斚w出,所以一旦有某个出口处没有释攑ֺ该释攄内存Q就会发生内存泄漏?
q义的说Q内存泄漏不仅仅包含堆内存的泄漏Q还包含pȝ资源的泄?resource leak)Q比如核心态HANDLEQGDI ObjectQSOCKETQInterface{,从根本上说这些由操作pȝ分配的对象也消耗内存,如果q些对象发生泄漏最l也会导致内存的泄漏。而且Q某些对象消耗的是核心态内存,q些对象严重泄漏时会D整个操作pȝ不稳定。所以相比之下,pȝ资源的泄漏比堆内存的泄漏更ؓ严重?
  GDI Object的泄漏是一U常见的资源泄漏Q?

void CMyView::OnPaint( CDC* pDC )
{
CBitmap bmp;
CBitmap* pOldBmp;
bmp.LoadBitmap(IDB_MYBMP);
pOldBmp = pDC->SelectObject( &bmp );

if( Something() ){
return;
}
pDC->SelectObject( pOldBmp );
return;
}

  当函数Something()q回非零的时候,E序在退出前没有把pOldBmp选回pDC中,q会DpOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长旉的运行,可能会导致整个系l花屏。这U问题在Win9x下比较容易暴露出来,因ؓWin9x的GDI堆比Win2k或NT的要很多?
内存泄漏的发生方式:
  以发生的方式来分c,内存泄漏可以分ؓ4c:
  1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行刎ͼ每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回TrueQ那么pOldBmp指向的HBITMAP对象L发生泄漏?
  2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作q程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回TrueQ那么pOldBmp指向的HBITMAP对象q不L发生泄漏。常发性和偶发性是相对的。对于特定的环境Q偶发性的也许变成了常发性的。所以测试环境和试ҎҎ内存泄漏至关重要?
  3. 一ơ性内存泄漏。发生内存泄漏的代码只会被执行一ơ,或者由于算法上的缺PDM有一块仅且一块内存发生泄漏。比如,在类的构造函C分配内存Q在析构函数中却没有释放该内存,但是因ؓq个cL一个SingletonQ所以内存泄漏只会发生一ơ。另一个例子:

char* g_lpszFileName = NULL;
void SetFileName( const char* lpcszFileName )
{
if( g_lpszFileName ){
free( g_lpszFileName );
}
g_lpszFileName = strdup( lpcszFileName );
}


  如果E序在结束的时候没有释放g_lpszFileName指向的字W串Q那么,即多次调用SetFileName()QM有一块内存,而且仅有一块内存发生泄漏?
  4. 隐式内存泄漏。程序在q行q程中不停的分配内存Q但是直到结束的时候才释放内存。严格的说这里ƈ没有发生内存泄漏Q因为最l程序释放了所有申L内存。但是对于一个服务器E序Q需要运行几天,几周甚至几个月,不及旉攑ֆ存也可能D最l耗尽pȝ的所有内存。所以,我们U这cd存泄漏ؓ隐式内存泄漏。D一个例子:

class Connection
{
public:
Connection( SOCKET s);
~Connection();

private:
SOCKET _socket;

};
class ConnectionManager
{
public:
ConnectionManager(){
}
~ConnectionManager(){
list<Connection>::iterator it;
for( it = _connlist.begin(); it != _connlist.end(); ++it ){
delete Q?itQ?
}
_connlist.clear();
}
void OnClientConnected( SOCKET s ){
Connection* p = new Connection(s);
_connlist.push_back(p);
}
void OnClientDisconnected( Connection* pconn ){
_connlist.remove( pconn );
delete pconn;
}
private:
list<Connection*> _connlist;
};


  假设在Client从Server端断开后,Serverq没有呼叫OnClientDisconnected()函数Q那么代表那ơ连接的Connection对象׃会被及时的删除(在ServerE序退出的时候,所有Connection对象会在ConnectionManager的析构函数里被删除)。当不断的有q接建立、断开旉式内存泄漏就发生了?
  从用户用程序的角度来看Q内存泄漏本w不会生什么危宻I作ؓ一般的用户Q根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆U,q会最l消耗尽pȝ所有的内存。从q个角度来说Q一ơ性内存泄漏ƈ没有什么危宻I因ؓ它不会堆U,而隐式内存泄漏危x则非常大,因ؓ较之于常发性和偶发性内存泄漏它更难被检到?/p> 内存泄漏:

  内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住q两个函敎ͼ我们p跟踪每一块内存的生命周期Q比如,每当成功的分配一块内存后Q就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这P当程序结束的时候,list中剩余的指针是指向那些没有被释攄内存。这里只是简单的描述了检内存泄漏的基本原理Q详l的法可以参见Steve Maguire?lt;<Writing Solid Code>>?
  如果要检堆内存的泄漏,那么需要截获住malloc/realloc/free和new/delete可以了Q其实new/delete最l也是用malloc/free的,所以只要截获前面一l即可)。对于其他的泄漏Q可以采用类似的ҎQ截获住相应的分配和释放函数。比如,要检BSTR的泄漏,需要截获SysAllocString/SysFreeStringQ要HMENU的泄漏,需要截获CreateMenu/ DestroyMenu。(有的资源的分配函数有多个Q释攑և数只有一个,比如QSysAllocStringLen也可以用来分配BSTRQ这时就需要截获多个分配函敎ͼ
  在Windowsq_下,内存泄漏的工具常用的一般有三种QMS C-Runtime Library内徏的检功能;外挂式的工P诸如QPurifyQBoundsChecker{;利用Windows NT自带的Performance Monitor。这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工兯弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码Q但是它能检出隐式的内存泄漏的存在Q这是其他两cdh能ؓ力的地方?
  以下我们详细讨论q三U检工P


VC下内存泄漏的方?

  用MFC开发的应用E序Q在DEBUG版模式下~译后,都会自动加入内存泄漏的检代码。在E序l束后,如果发生了内存泄漏,在DebugH口中会昄出所有发生泄漏的内存块的信息Q以下两行显CZ一块被泄漏的内存块的信息:
E:\TestMemLeak\TestDlg.cpp(70) : {59} normal block at 0x00881710, 200 bytes long.
Data: <abcdefghijklmnop> 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70
  W一行显C内存块由TestDlg.cpp文gQ第70行代码分配,地址?x00881710Q大ؓ200字节Q{59}是指调用内存分配函数的Request OrderQ关于它的详l信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行昄该内存块?6个字节的内容Q尖括号内是以ASCII方式昄Q接着的是?6q制方式昄?
  一般大安误以些内存泄漏的功能是由MFC提供的,其实不然。MFC只是装和利用了MS C-Runtime Library的Debug Function。非MFCE序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检功能。MS C-Runtime Library在实现malloc/freeQstrdup{函数时已经内徏了内存泄漏的功能?
  注意观察一下由MFC Application Wizard生成的项目,在每一个cpp文g的头部都有这样一D宏定义Q?
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
  有了q样的定义,在编译DEBUG版时Q出现在q个cpp文g中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢QDEBUG_NEW也是一个宏Q以下摘自afx.hQ?632?
#define DEBUG_NEW new(THIS_FILE, __LINE__)
所以如果有q样一行代码:
char* p = new char[200];
l过宏替换就变成了:
char* p = new( THIS_FILE, __LINE__)char[200];
ҎC++的标准,对于以上的new的用方法,~译器会Lq样定义的operator newQ?
void* operator new(size_t, LPCSTR, int)
我们在afxmem.cpp 63行找C一个这Loperator new 的实?/p>
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine)
{
return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
}
void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine)
{

pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);
if (pResult != NULL)
return pResult;

}


  W二个operator new函数比较长,Z单期_我只摘录了部分。很昄最后的内存分配q是通过_malloc_dbg函数实现的,q个函数属于MS C-Runtime Library 的Debug Function。这个函C但要求传入内存的大小Q另外还有文件名和行号两个参数。文件名和行号就是用来记录此ơ分配是由哪一D代码造成的。如果这块内存在E序l束之前没有被释放,那么q些信息׃输出到DebugH口里?
  q里Z提一下THIS_FILEQ__FILE和__LINE__。__FILE__和__LINE__都是~译器定义的宏。当到__FILE__Ӟ~译器会把__FILE__替换成一个字W串Q这个字W串是当前在编译的文g的\径名。当到__LINE__Ӟ~译器会把__LINE__替换成一个数字,q个数字是当前q行代码的行受在DEBUG_NEW的定义中没有直接使用__FILE__Q而是用了THIS_FILEQ其目的是ؓ了减目标文件的大小。假讑֜某个cpp文g中有100处用了newQ如果直接用__FILE__Q那~译器会产生100个常量字W串Q这100个字W串都是q个cpp文g的\径名Q显然十分冗余。如果用THIS_FILEQ编译器只会产生一个常量字W串Q那100处new的调用用的都是指向帔R字符串的指针?
  再次观察一下由MFC Application Wizard生成的项目,我们会发现在cpp文g中只对new做了映射Q如果你在程序中直接使用malloc函数分配内存Q调用malloc的文件名和行h不会被记录下来的。如果这块内存发生了泄漏QMS C-Runtime Library仍然能检到Q但是当输出q块内存块的信息Q不会包含分配它的的文g名和行号?
要在非MFCE序中打开内存泄漏的检功能非常容易,你只要在E序的入口处加入以下几行代码Q?

int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
_CrtSetDbgFlag( tmpFlag );


  q样Q在E序l束的时候,也就是winmainQmain或dllmain函数q回之后Q如果还有内存块没有释放Q它们的信息会被打印到DebugH口里?
如果你试着创徏了一个非MFC应用E序Q而且在程序的入口处加入了以上代码Qƈ且故意在E序中不释放某些内存块,你会在DebugH口里看C下的信息Q?
{47} normal block at 0x00C91C90, 200 bytes long.
Data: < > 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
  内存泄漏的确到了,但是和上面MFCE序的例子相比,~少了文件名和行受对于一个比较大的程序,没有q些信息Q解决问题将变得十分困难?
  Z能够知道泄漏的内存块是在哪里分配的,你需要实现类似MFC的映功能,把newQmaolloc{函数映到_malloc_dbg函数上。这里我不再赘述Q你可以参考MFC的源代码?
  ׃Debug Function实现在MS C-RuntimeLibrary中,所以它只能到堆内存的泄漏Q而且只限于mallocQrealloc或strdup{分配的内存Q而那些系l资源,比如HANDLEQGDI ObjectQ或是不通过C-Runtime Library分配的内存,比如VARIANTQBSTR的泄漏,它是无法到的,q是q种法的一个重大的局限性。另外,Z能记录内存块是在哪里分配的,源代码必ȝ应的配合Q这在调试一些老的E序非常ȝQ毕竟修Ҏ代码不是一件省心的事,q是q种法的另一个局限性?
  对于开发一个大型的E序QMS C-Runtime Library提供的检功能是q远不够的。接下来我们q看外挂式的检工兗我用的比较多的是BoundsCheckerQ一则因为它的功能比较全面,更重要的是它的稳定性。这cd具如果不E_Q反而会忙里Mؕ。到底是鼎鼎大名的NuMegaQ我用下来基本上没有什么大问题?/p> 使用BoundsChecker内存泄漏:

  BoundsChecker采用一U被UCؓCode Injection的技术,来截获对分配内存和释攑ֆ存的函数的调用。简单地_当你的程序开始运行时QBoundsChecker的DLL被自动蝲入进E的地址I间Q这可以通过system-level的Hook实现Q,然后它会修改q程中对内存分配和释攄函数调用Q让q些调用首先转入它的代码Q然后再执行原来的代码。BoundsChecker在做q些动作的时Q无M改被调试E序的源代码或工E配|文Ӟq得用它非常的简ѝ直接?
  q里我们以malloc函数ZQ截获其他的函数Ҏ与此cM?
  需要被截获的函数可能在DLL中,也可能在E序的代码里。比如,如果静态连lC-Runtime LibraryQ那么malloc函数的代码会被连l到E序里。ؓ了截获住对这cd数的调用QBoundsChecker会动态修改这些函数的指o?
  以下两段汇编代码Q一D|有BoundsChecker介入Q另一D则有BoundsChecker的介入:
126: _CRTIMP void * __cdecl malloc (
127: size_t nSize
128: )
129: {
00403C10 push ebp
00403C11 mov ebp,esp
130: return _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);
00403C13 push 0
00403C15 push 0
00403C17 push 1
00403C19 mov eax,[__newmode (0042376c)]
00403C1E push eax
00403C1F mov ecx,dword ptr [nSize]
00403C22 push ecx
00403C23 call _nh_malloc_dbg (00403c80)
00403C28 add esp,14h
131: }


  以下q一D代码有BoundsChecker介入Q?/p>
126: _CRTIMP void * __cdecl malloc (
127: size_t nSize
128: )
129: {
00403C10 jmp 01F41EC8
00403C15 push 0
00403C17 push 1
00403C19 mov eax,[__newmode (0042376c)]
00403C1E push eax
00403C1F mov ecx,dword ptr [nSize]
00403C22 push ecx
00403C23 call _nh_malloc_dbg (00403c80)
00403C28 add esp,14h
131: }

  当BoundsChecker介入后,函数malloc的前三条汇编指o被替换成一条jmp指oQ原来的三条指o被搬到地址01F41EC8处了。当E序q入malloc后先jmp?1F41EC8Q执行原来的三条指oQ然后就是BoundsChecker的天下了。大致上它会先记录函数的q回地址Q函数的q回地址在stack上,所以很Ҏ修改Q,然后把返回地址指向属于BoundsChecker的代码,接着跛_malloc函数原来的指令,也就是在00403c15的地斏V当malloc函数l束的时候,׃q回地址被修改,它会q回到BoundsChecker的代码中Q此时BoundsChecker会记录由malloc分配的内存的指针Q然后再跌{到到原来的返回地址厅R?
  如果内存分配/释放函数在DLL中,BoundsChecker则采用另一U方法来截获对这些函数的调用。BoundsChecker通过修改E序的DLL Import Table让table中的函数地址指向自己的地址Q以辑ֈ截获的目的。关于如何拦截Windows的系l函敎ͼ《程序员》杂?002q?期,《API钩子揭密Q下Q》,对修改导入地址表做了概要的描述。我׃再赘q?
  截获住这些分配和释放函数QBoundsCheckerp记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关Q也是说当BoundsChecker到内存泄漏Q它如何报告q块内存块是哪段代码分配的。答案是调试信息QDebug InformationQ。当我们~译一个Debug版的E序Ӟ~译器会把源代码和二q制代码之间的对应关p记录下来,攑ֈ一个单独的文g?.pdb)或者直接连l进目标E序中。有了这些信息,调试器才能完成断点设|,单步执行Q查看变量等功能。BoundsChecker支持多种调试信息格式Q它通过直接d调试信息p得到分配某块内存的源代码在哪个文Ӟ哪一行上。用Code Injection和Debug InformationQBoundsChecker不但能记录呼叫分配函数的源代码的位置Q而且q能记录分配时的Call StackQ以及Call Stack上的函数的源代码位置。这在用像MFCq样的类库时非常有用Q以下我用一个例子来说明Q?

void ShowXItemMenu()
{

CMenu menu;
menu.CreatePopupMenu();
//add menu items.
menu.TrackPropupMenu();

}
void ShowYItemMenu( )
{

CMenu menu;
menu.CreatePopupMenu();
//add menu items.
menu.TrackPropupMenu();
menu.Detach();//this will cause HMENU leak

}
BOOL CMenu::CreatePopupMenu()
{

hMenu = CreatePopupMenu();

}


  当调用ShowYItemMenu()Ӟ我们故意造成HMENU的泄漏。但是,对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假讄你的E序有许多地方用了CMenu的CreatePopupMenu()函数Q如果只是告诉你泄漏是由CMenu::CreatePopupMenu()造成的,你依然无法确认问题的根结到底在哪里,在ShowXItemMenu()中还是在ShowYItemMenu()中,或者还有其它的地方也用了CreatePopupMenu()Q有了Call Stack的信息,问题容易了。BoundsChecker会如下报告泄漏的HMENU的信息:

 

Function
File
Line

CMenu::CreatePopupMenu
E:\8168\vc98\mfc\mfc\include\afxwin1.inl
1009

ShowYItemMenu
E:\testmemleak\mytest.cpp
100

 

  q里省略了其他的函数调用

  如此Q我们很Ҏ扑ֈ发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编E时Q大部分的API调用都被装在类库的class里,有了Call Stack信息Q我们就可以非常Ҏ的追t到真正发生泄漏的代码?
  记录Call Stack信息会ɽE序的运行变得非常慢Q因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开养I
1. 打开菜单QBoundsChecker|Setting…
2. 在Error Detection中Q在Error Detection Scheme的List中选择Custom
3. 在Category的Combox中选择Pointer and leak error check
4. 钩上Report Call Stack复选框
5. 点击Ok
  ZCode InjectionQBoundsCheckerq提供了API Parameter的校验功能,memory over run{功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题,所以不在此详述了?
  管BoundsChecker的功能如此强大,但是面对隐式内存泄漏仍然昑־苍白无力。所以接下来我们看看如何用Performance Monitor内存泄漏?


使用Performance Monitor内存泄?/strong>

  NT的内核在设计q程中已l加入了pȝ监视功能Q比如CPU的用率Q内存的使用情况QI/O操作的频J度{都作ؓ一个个CounterQ应用程序可以通过dq些Counter了解整个pȝ的或者某个进E的q行状况。Performance Monitor是q样一个应用程序?
  Z内存泄漏,我们一般可以监视Process对象的Handle CountQVirutal Bytes 和Working Set三个Counter。Handle Count记录了进E当前打开的HANDLE的个敎ͼ监视q个Counter有助于我们发现程序是否有Handle泄漏QVirtual Bytes记录了该q程当前在虚地址I间上用的虚拟内存的大,NT的内存分配采用了两步走的ҎQ首先,在虚地址I间上保留一D늩_q时操作pȝq没有分配物理内存,只是保留了一D地址。然后,再提交这D늩_q时操作pȝ才会分配物理内存。所以,Virtual Bytes一般d于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系l底层的问题; Working Set记录了操作系lؓq程已提交的内存的总量Q这个值和E序甌的内存总量存在密切的关p,如果E序存在内存的泄漏这个g持箋增加Q但是Virtual Bytes却是跌式增加的?
  监视q些Counter可以让我们了解进E用内存的情况Q如果发生了泄漏Q即使是隐式内存泄漏Q这些Counter的g会持l增加。但是,我们知道有问题却不知道哪里有问题Q所以一般用Performance Monitor来验证是否有内存泄漏Q而用BoundsChecker来找到和解决问题?
  当Performance Monitor昄有内存泄漏,而BoundsChecker却无法检到Q这时有两种可能Q第一U,发生了偶发性内存泄漏。这时你要确保用Performance Monitor和用BoundsCheckerӞE序的运行环境和操作Ҏ是一致的。第二种Q发生了隐式的内存泄漏。这时你要重新审查程序的设计Q然后仔l研IPerformance Monitor记录的Counter的值的变化图,分析其中的变化和E序q行逻辑的关p,扑ֈ一些可能的原因。这是一个痛苦的q程Q充满了假设、猜惟뀁验证、失败,但这也是一个积累经验的l好Z?/p>

fengmin 2008-04-03 22:28 发表评论
]]>
ŷþþXXX| һƷþ| ŷþ18| þþƷAVһ| ޳ɫwwwþվҹ| þۺɫ| þþþþۺһĻ| ɫþþþSWAGƷ| þþƷ| þùҹaӰԺ| þþƷAVһ| þþùƷ| 99þ99þþƷƬ| ޾Ʒ99þ| þþøƵ| ƷþþþþҰ| þþƷһAV| ޹˾þۺһ| þ×Ʒþþþþ| ŷ˾þۺ| ˾þƵ| ˾ƷѾþþþ| ŷһþþƷ| þ޸ۺ| þۺ˿ձ| 97þۺɫۺɫhd| þþƷAVһ| ޾Ʒþþþþ | 99þùۺϾƷŮͬͼƬ | 99þۺϾƷ| þþŷղ| þﶼǾƷ| һձ˾þۺӰ| þþƷ| þӰӹ| þþþþϸ| ŷ龫Ʒþþþþþþžž| 69Ʒþþþ9999| ƷŮþþ| ۲ӰԺþþƷ| 97Ʒ˾þþô߽|