| 導購 | 订阅 | 在线投稿
分享
 
 
當前位置: 王朝網路 >> c/c++ >> 淺談C/C++內存泄漏及其檢測工具
 

淺談C/C++內存泄漏及其檢測工具

2008-06-01 02:08:14  編輯來源:互聯網  简体版  手機版  評論  字體: ||
 
 
  對于一個c/c++程序員來說,內存泄漏是一個常見的也是令人頭疼的問題。已經有許多技術被研究出來以應對這個問題,比如Smart Pointer,Garbage Collection等。Smart Pointer技術比較成熟,STL中已經包含支持Smart Pointer的class,但是它的使用似乎並不廣泛,而且它也不能解決所有的問題;Garbage Collection技術在Java中已經比較成熟,但是在c/c++領域的發展並不順暢,雖然很早就有人思考在C++中也加入GC的支持。現實世界就是這樣的,作爲一個c/c++程序員,內存泄漏是你心中永遠的痛。不過好在現在有許多工具能夠幫助我們驗證內存泄漏的存在,找出發生問題的代碼。

  內存泄漏的定義

  一般我們常說的內存泄漏是指堆內存的泄漏。堆內存是指程序從堆中分配的,大小任意的(內存塊的大小可以在程序運行期決定),使用完後必須顯示釋放的內存。應用程序一般使用malloc,realloc,new等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用free或delete釋放該內存塊,否則,這塊內存就不能被再次使用,我們就說這塊內存泄漏了。以下這段小程序演示了堆內存發生泄漏的情形:

  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()返回零的時候,指針p指向的內存就不會被釋放。這是一種常見的發生內存泄漏的情形。程序在入口處分配內存,在出口處釋放內存,但是c函數可以在任何地方退出,所以一旦有某個出口處沒有釋放應該釋放的內存,就會發生內存泄漏。

  廣義的說,內存泄漏不僅僅包含堆內存的泄漏,還包含系統資源的泄漏(resource leak),比如核心態HANDLE,GDI Object,SOCKET, Interface等,從根本上說這些由操作系統分配的對象也消耗內存,假如這些對象發生泄漏最終也會導致內存的泄漏。而且,某些對象消耗的是核心態內存,這些對象嚴重泄漏時會導致整個操作系統不穩定。所以相比之下,系統資源的泄漏比堆內存的泄漏更爲嚴重。

  GDI Object的泄漏是一種常見的資源泄漏:

  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()返回非零的時候,程序在退出前沒有把pOldBmp選回pDC中,這會導致pOldBmp指向的HBITMAP對象發生泄漏。這個程序假如長時間的運行,可能會導致整個系統花屏。這種問題在Win9x下比較輕易暴露出來,因爲Win9x的GDI堆比Win2k或NT的要小很多。

  內存泄漏的發生方式:

  以發生的方式來分類,內存泄漏可以分爲4類:

  1. 常發性內存泄漏。發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。比如例二,假如Something()函數一直返回True,那麽pOldBmp指向的HBITMAP對象總是發生泄漏。

  2. 偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。比如例二,假如Something()函數只有在特定環境下才返回True,那麽pOldBmp指向的HBITMAP對象並不總是發生泄漏。常發性和偶發性是相對的。對于特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。

  3. 一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者由于算法上的缺陷,導致總會有一塊僅且一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,但是因爲這個類是一個Singleton,所以內存泄漏只會發生一次。另一個例子:

  char* g_lpszFileName = NULL;

  void SetFileName( const char* lpcszFileName )

  {

  if( g_lpszFileName ){

  free( g_lpszFileName );

  }

  g_lpszFileName = strdup( lpcszFileName );

  }

  例三

  假如程序在結束的時候沒有釋放g_lpszFileName指向的字符串,那麽,即使多次調用SetFileName(),總會有一塊內存,而且僅有一塊內存發生泄漏。

  4. 隱式內存泄漏。程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這裏並沒有發生內存泄漏,因爲最終程序釋放了所有申請的內存。但是對于一個服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏爲隱式內存泄漏。舉一個例子:

  class Connection

  {

  public:

  Connection( SOCKET s);

  ~Connection();

  …

  private:

  SOCKET _socket;

  …

  };

  class ConnectionManager

  {

  public:

  

   ConnectionManager(){}

  ~ConnectionManager(){

  list::iterator it;

  for( it = _connlist.begin(); it != _connlist.end(); ++it ){

  delete (*it);

  }

  _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 _connlist;

  };

  例四

  假設在Client從Server端斷開後,Server並沒有呼叫OnClientDisconnected()函數,那麽代表那次連接的Connection對象就不會被及時的刪除(在Server程序退出的時候,所有Connection對象會在ConnectionManager的析構函數裏被刪除)。當不斷的有連接建立、斷開時隱式內存泄漏就發生了。

  從用戶使用程序的角度來看,內存泄漏本身不會産生什麽危害,作爲一般的用戶,根本感覺不到內存泄漏的存在。真正有危害的是內存泄漏的堆積,這會最終消耗盡系統所有的內存。從這個角度來說,一次性內存泄漏並沒有什麽危害,因爲它不會堆積,而隱式內存泄漏危害性則非常大,因爲較之于常發性和偶發性內存泄漏它更難被檢測到。 Photoshop教程 數據結構 五筆輸入法專題 QQ病毒專題 共享上網專題 Google工具和服務專題 檢測內存泄漏

  檢測內存泄漏的要害是要能截獲住對分配內存和釋放內存的函數的調用。截獲住這兩個函數,我們就能跟蹤每一塊內存的生命周期,比如,每當成功的分配一塊內存後,就把它的指針加入一個全局的list中;每當釋放一塊內存,再把它的指針從list中刪除。這樣,當程序結束的時候,list中剩余的指針就是指向那些沒有被釋放的內存。這裏只是簡單的描述了檢測內存泄漏的基本原理,具體的算法可以參見Steve Maguire的<<Writing Solid Code>>。

  假如要檢測堆內存的泄漏,那麽需要截獲住malloc/realloc/free和new/delete就可以了(其實new/delete最終也是用malloc/free的,所以只要截獲前面一組即可)。對于其他的泄漏,可以采用類似的方法,截獲住相應的分配和釋放函數。比如,要檢測BSTR的泄漏,就需要截獲SysAllocString/SysFreeString;要檢測HMENU的泄漏,就需要截獲CreateMenu/ DestroyMenu。(有的資源的分配函數有多個,釋放函數只有一個,比如,SysAllocStringLen也可以用來分配BSTR,這時就需要截獲多個分配函數)

  在Windows平台下,檢測內存泄漏的工具常用的一般有三種,MS C-Runtime Library內建的檢測功能;外挂式的檢測工具,諸如,Purify,BoundsChecker等;利用Windows NT自帶的Performance Monitor。這三種工具各有優缺點,MS C-Runtime Library雖然功能上較之外挂式的工具要弱,但是它是免費的;Performance Monitor雖然無法標示出發生問題的代碼,但是它能檢測出隱式的內存泄漏的存在,這是其他兩類工具無能爲力的地方。

  以下我們具體討論這三種檢測工具:

  VC下內存泄漏的檢測方法

  用MFC開發的應用程序,在DEBUG版模式下編譯後,都會自動加入內存泄漏的檢測代碼。在程序結束後,假如發生了內存泄漏,在Debug窗口中會顯示出所有發生泄漏的內存塊的信息,以下兩行顯示了一塊被泄漏的內存塊的信息:

  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

  第一行顯示該內存塊由TestDlg.cpp文件,第70行代碼分配,地址在0x00881710,大小爲200字節,{59}是指調用內存分配函數的Request Order,關于它的具體信息可以參見MSDN中_CrtSetBreakAlloc()的幫助。第二行顯示該內存塊前16個字節的內容,尖括號內是以ASCII方式顯示,接著的是以16進制方式顯示。

  

  

  一般大家都誤以爲這些內存泄漏的檢測功能是由MFC提供的,其實不然。MFC只是封裝和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入內存泄漏的檢測功能。MS C-Runtime Library在實現malloc/free,strdup等函數時已經內建了內存泄漏的檢測功能。

  注重觀察一下由MFC Application Wizard生成的項目,在每一個cpp文件的頭部都有這樣一段宏定義:

  #ifdef _DEBUG

  #define new DEBUG_NEW

  #undef THIS_FILE

  static char THIS_FILE[] = __FILE__;

  #endif

  有了這樣的定義,在編譯DEBUG版時,出現在這個cpp文件中的所有new都被替換成DEBUG_NEW了。那麽DEBUG_NEW是什麽呢?DEBUG_NEW也是一個宏,以下摘自afx.h,1632行

  #define DEBUG_NEW new(THIS_FILE, __LINE__)

  所以假如有這樣一行代碼:

  char* p = new char[200];

  經過宏替換就變成了:

  char* p = new( THIS_FILE, __LINE__)char[200];

  根據C++的標准,對于以上的new的使用方法,編譯器會去找這樣定義的operator new:

  void* operator new(size_t, LPCSTR, int)

  我們在afxmem.cpp 63行找到了一個這樣的operator new 的實現

  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;

  …

  }

  第二個operator new函數比較長,爲了簡單期間,我只摘錄了部分。很顯然最後的內存分配還是通過_malloc_dbg函數實現的,這個函數屬于MS C-Runtime Library 的Debug Function。這個函數不但要求傳入內存的大小,另外還有文件名和行號兩個參數。文件名和行號就是用來記錄此次分配是由哪一段代碼造成的。假如這塊內存在程序結束之前沒有被釋放,那麽這些信息就會輸出到Debug窗口裏。

  這裏順便提一下THIS_FILE,__FILE和__LINE__。__FILE__和__LINE__都是編譯器定義的宏。當碰到__FILE__時,編譯器會把__FILE__替換成一個字符串,這個字符串就是當前在編譯的文件的路徑名。當碰到__LINE__時,編譯器會把__LINE__替換成一個數字,這個數字就是當前這行代碼的行號。在DEBUG_NEW的定義中沒有直接使用__FILE__,而是用了THIS_FILE,其目的是爲了減小目標文件的大小。假設在某個cpp文件中有100處使用了new,假如直接使用__FILE__,那編譯器會産生100個常量字符串,這100個字符串都是飧?/SPAN>cpp文件的路徑名,顯然十分冗余。假如使用THIS_FILE,編譯器只會産生一個常量字符串,那100處new的調用使用的都是指向常量字符串的指針。

  再次觀察一下由MFC Application Wizard生成的項目,我們會發現在cpp文件中只對new做了映射,假如你在程序中直接使用malloc函數分配內存,調用malloc的文件名和行號是不會被記錄下來的。假如這塊內存發生了泄漏,MS C-Runtime Library仍然能檢測到,但是當輸出這塊內存塊的信息,不會包含分配它的的文件名和行號。

  要在非MFC程序中打開內存泄漏的檢測功能非常輕易,你只要在程序的入口處加入以下幾行代碼:

  int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

  tmpFlag = _CRTDBG_LEAK_CHECK_DF;

  _CrtSetDbgFlag( tmpFlag );

  這樣,在程序結束的時候,也就是winmain,main或dllmain函數返回之後,假如還有內存塊沒有釋放,它們的信息會被打印到Debug窗口裏。

  假如你試著創建了一個非MFC應用程序,而且在程序的入口處加入了以上代碼,並且故意在程序中不釋放某些內存塊,你會在Debug窗口裏看到以下的信息:

  {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

  內存泄漏的確檢測到了,但是和上面MFC程序的例子相比,缺少了文件名和行號。對于一個比較大的程序,沒有這些信息,解決問題將變得十分困難。

  爲了能夠知道泄漏的內存塊是在哪裏分配的,你需要實現類似MFC的映射功能,把new,maolloc等函數映射到_malloc_dbg函數上。這裏我不再贅述,你可以參考MFC的源代碼。

  由于Debug Function實現在MS C-RuntimeLibrary中,所以它只能檢測到堆內存的泄漏,而且只限于malloc,realloc或strdup等分配的內存,而那些系統資源,比如HANDLE,GDI Object,或是不通過C-Runtime Library分配的內存,比如VARIANT,BSTR的泄漏,它是無法檢測到的,這是這種檢測法的一個重大的局限性。另外,爲了能記錄內存塊是在哪裏分配的,源代碼必須相應的配合,這在調試一些老的程序非常麻煩,究竟修改源代碼不是一件省心的事,這是這種檢測法的另一個局限性。

  對于開發一個大型的程序,MS C-Runtime Library提供的檢測功能是遠遠不夠的。接下來我們就看看外挂式的檢測工具。我用的比較多的是BoundsChecker,一則因爲它的功能比較全面,更重要的是它的穩定性。這類工具假如不穩定,反而會忙裏添亂。到底是出自鼎鼎大名的NuMega,我用下來基本上沒有什麽大問題。 Photoshop教程 數據結構 五筆輸入法專題 QQ病毒專題 共享上網專題 Google工具和服務專題

  

   使用BoundsChecker檢測內存泄漏:

  BoundsChecker采用一種被稱爲 Code Injection的技術,來截獲對分配內存和釋放內存的函數的調用。簡單地說,當你的程序開始運行時,BoundsChecker的DLL被自動載入進程的地址空間(這可以通過system-level的Hook實現),然後它會修改進程中對內存分配和釋放的函數調用,讓這些調用首先轉入它的代碼,然後再執行原來的代碼。BoundsChecker在做這些動作的時,無須修改被調試程序的源代碼或工程配置文件,這使得使用它非常的簡便、直接。

  這裏我們以malloc函數爲例,截獲其他的函數方法與此類似。

  需要被截獲的函數可能在DLL中,也可能在程序的代碼裏。比如,假如靜態連結C-Runtime Library,那麽malloc函數的代碼會被連結到程序裏。爲了截獲住對這類函數的調用,BoundsChecker會動態修改這些函數的指令。

  以下兩段彙編代碼,一段沒有BoundsChecker介入,另一段則有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: }

  以下這一段代碼有BoundsChecker介入:

  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的前三條彙編指令被替換成一條jmp指令,原來的三條指令被搬到地址01F41EC8處了。當程序進入malloc後先jmp到01F41EC8,執行原來的三條指令,然後就是BoundsChecker的天下了。大致上它會先記錄函數的返回地址(函數的返回地址在stack上,所以很輕易修改),然後把返回地址指向屬于BoundsChecker的代碼,接著跳到malloc函數原來的指令,也就是在00403c15的地方。當malloc函數結束的時候,由于返回地址被修改,它會返回到BoundsChecker的代碼中,此時BoundsChecker會記錄由malloc分配的內存的指針,然後再跳轉到到原來的返回地址去。

  假如內存分配/釋放函數在DLL中,BoundsChecker則采用另一種方法來截獲對這些函數的調用。BoundsChecker通過修改程序的DLL Import Table讓table中的函數地址指向自己的地址,以達到截獲的目的。

  截獲住這些分配和釋放函數,BoundsChecker就能記錄被分配的內存或資源的生命周期。接下來的問題是如何與源代碼相關,也就是說當BoundsChecker檢測到內存泄漏,它如何報告這塊內存塊是哪段代碼分配的。答案是調試信息(Debug Information)。當我們編譯一個Debug版的程序時,編譯器會把源代碼和二進制代碼之間的對應關系記錄下來,放到一個單獨的文件裏(.pdb)或者直接連結進目標程序,通過直接讀取調試信息就能得到分配某塊內存的源代碼在哪個文件,哪一行上。使用Code Injection和Debug Information,使BoundsChecker不但能記錄呼叫分配函數的源代碼的位置,而且還能記錄分配時的Call Stack,以及Call Stack上的函數的源代碼位置。這在使用像MFC這樣的類庫時非常有用,以下我用一個例子來說明:

  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()中分配的。假設的你的程序有許多地方使用了CMenu的CreatePopupMenu()函數,如CMenu::CreatePopupMenu()造成的,你依然無法確認問題的根結到底在哪裏,在ShowXItemMenu()中還是在ShowYItemMenu()中,或者還有其它的地方也使用了CreatePopupMenu()?有了Call Stack的信息,問題就輕易了。BoundsChecker會如下報告泄漏的HMENU的信息:

  

  

  Function

  File

  Line

  CMenu::CreatePopupMenu

  E:\8168\vc98\mfc\mfc\include\afxwin1.inl

  1009

  ShowYItemMenu

  E:\testmemleak\mytest.cpp

  100

  這裏省略了其他的函數調用

  如此,我們很輕易找到發生問題的函數是ShowYItemMenu()。當使用MFC之類的類庫編程時,大部分的API調用都被封裝在類庫的class裏,有了Call Stack信息,我們就可以非常輕易的追蹤到真正發生泄漏的代碼。

  記錄Call Stack信息會使程序的運行變得非常慢,因此默認情況下BoundsChecker不會記錄Call Stack信息。可以按照以下的步驟打開記錄Call Stack信息的選項開關:

  1. 打開菜單:BoundsCheckerSetting…

  2. 在Error Detection頁中,在Error Detection Scheme的List中選擇Custom

  3. 在Category的Combox中選擇 Pointer and leak error check

  4. 鈎上Report Call Stack複選框

  5. 點擊Ok

  基于Code Injection,BoundsChecker還提供了API Parameter的校驗功能,memory over run等功能。這些功能對于程序的開發都非常有益。由于這些內容不屬于本文的主題,所以不在此詳述了。

  盡管BoundsChecker的功能如此強大,但是面對隱式內存泄漏仍然顯得蒼白無力。所以接下來我們看看如何用Performance Monitor檢測內存泄漏。

  使用Performance Monitor檢測內存泄漏

  NT的內核在設計過程中已經加入了系統監視功能,比如CPU的使用率,內存的使用情況,I/O操作的頻繁度等都作爲一個個Counter,應用程序可以通過讀取這些Counter了解整個系統的或者某個進程的運行狀況。Performance Monitor就是這樣一個應用程序。

  爲了檢測內存泄漏,我們一般可以監視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卻是跳躍式增加的。

  監視這些Counter可以讓我們了解進程使用內存的情況,假如發生了泄漏,即使是隱式內存泄漏,這些Counter的值也會持續增加。但是,我們知道有問題卻不知道哪裏有問題,所以一般使用Performance Monitor來驗證是否有內存泄漏,而使用BoundsChecker來找到和解決。

  當Performance Monitor顯示有內存泄漏,而BoundsChecker卻無法檢測到,這時有兩種可能:第一種,發生了偶發性內存泄漏。這時你要確保使用Performance Monitor和使用BoundsChecker時,程序的運行環境和操作方法是一致的。第二種,發生了隱式的內存泄漏。這時你要重新審查程序的設計,然後仔細研究Performance Monitor記錄的Counter的值的變化圖,分析其中的變化和程序運行邏輯的關系,找到一些可能的原因。這是一個痛苦的過程,布滿了假設、猜想、驗證、失敗,但這也是一個積累經驗的絕好機會。

  總結

  內存泄漏是個大而複雜的問題,即使是Java和.Net這樣有Gabarge Collection機制的環境,也存在著泄漏的可能,比如隱式內存泄漏。由于篇幅和能力的限制,本文只能對這個主題做一個粗淺的研究。其他的問題,比如多模塊下的泄漏檢測,如何在程序運行時對內存使用情況進行分析等等,都是可以深入研究的題目。假如您有什麽想法,建議或發現了某些錯誤,歡迎和我交流。
 
 
 
上一篇《C++內存管理詳解》
下一篇《C++跨平台遊戲開發之ClanLibSDK》
 
 
 
 
 
 
日版寵物情人插曲《Winding Road》歌詞

日版寵物情人2017的插曲,很帶節奏感,日語的,女生唱的。 最後聽見是在第8集的時候女主手割傷了,然後男主用嘴幫她吸了一下,插曲就出來了。 歌手:Def...

兄弟共妻,我成了他們夜裏的美食

老鍾家的兩個兒子很特別,就是跟其他的人不太一樣,魔一般的執著。兄弟倆都到了要結婚的年齡了,不管自家老爹怎麽磨破嘴皮子,兄弟倆說不娶就不娶,老父母爲兄弟兩操碎了心...

如何磨出破洞牛仔褲?牛仔褲怎麽剪破洞?

把牛仔褲磨出有線的破洞 1、具體工具就是磨腳石,下面墊一個硬物,然後用磨腳石一直磨一直磨,到把那塊磨薄了,用手撕開就好了。出來的洞啊很自然的。需要貓須的話調幾...

我就是掃描下圖得到了敬業福和愛國福

先來看下敬業福和愛國福 今年春節,支付寶再次推出了“五福紅包”活動,表示要“把欠大家的敬業福都還給大家”。 今天該活動正式啓動,和去年一樣,需要收集“五福”...

冰箱異味産生的原因和臭味去除的方法

有時候我們打開冰箱就會聞到一股異味,冰箱裏的這種異味是因爲一些物質發出的氣味的混合體,聞起來讓人惡心。 産生這些異味的主要原因有以下幾點。 1、很多人有這種習...

《極品家丁》1-31集大結局分集劇情介紹

簡介 《極品家丁》講述了現代白領林晚榮無意回到古代金陵,並追隨蕭二小姐化名“林三”進入蕭府,不料卻陰差陽錯上演了一出低級家丁拼搏上位的“林三升職記”。...

李溪芮《極品家丁》片尾曲《你就是我最愛的寶寶》歌詞

你就是我最愛的寶寶 - 李溪芮 (電視劇《極品家丁》片尾曲) 作詞:常馨內 作曲:常馨內 你的眉 又鬼馬的挑 你的嘴 又壞壞的笑 上一秒吵鬧 下...

烏梅的功效與作用以及烏梅的食用禁忌有哪些?

烏梅,又稱春梅,中醫認爲,烏梅味酸,性溫,無毒,具有安心、除熱、下氣、祛痰、止渴調中、殺蟲的功效,治肢體痛、肺痨病。烏梅泡水喝能治傷寒煩熱、止吐瀉,與幹姜一起制...

什麽是脂肪粒?如何消除臉部脂肪粒?

什麽是脂肪粒 在我們的臉上總會長一個個像脂肪的小顆粒,弄也弄不掉,而且顔色還是白白的。它既不是粉刺也不是其他的任何痘痘,它就是脂肪粒。 脂肪粒雖然也是由油脂...

網絡安全治理:國家安全保障的主要方向是打擊犯罪,而不是處置和懲罰受害者

來源:中國青年報 新的攻擊方法不斷湧現,黑客幾乎永遠占據網絡攻擊的上風,我們不可能通過技術手段杜絕網絡攻擊。國家安全保障的主要方向是打擊犯罪,而不是處置和懲罰...

河南夫妻在溫嶺網絡直播“造人”內容涉黃被刑事拘留

夫妻網絡直播“造人”爆紅   1月9日,溫嶺城北派出所接到南京警方的協查通告,他們近期打掉了一個涉黃直播APP平台。而根據掌握的線索,其中有一對涉案的夫妻主播...

如何防止牆紙老化?牆紙變舊變黃怎麽辦?

如何防止牆紙老化? (1)選擇透氣性好的牆紙 市場上牆紙的材質分無紡布的、木纖維的、PVC的、玻璃纖維基材的、布面的等,相對而言,PVC材質的牆紙最不透氣...

鮮肌之謎非日本生産VS鮮肌之謎假日貨是謠言

觀點一:破日本銷售量的“鮮肌之謎” 非日本生産 近一段時間,淘寶上架了一款名爲“鮮肌之謎的” 鲑魚卵巢美容液,號稱是最近日本的一款推出的全新護膚品,産品本身所...

中國最美古詩詞精選摘抄

系腰裙(北宋詞人 張先) 惜霜蟾照夜雲天,朦胧影、畫勾闌。人情縱似長情月,算一年年。又能得、幾番圓。 欲寄西江題葉字,流不到、五亭前。東池始有荷新綠,尚小如...

關于女人的經典語句

關于女人的經典語句1、【做一個獨立的女人】 思想獨立:有主見、有自己的人生觀、價值觀。有上進心,永遠不放棄自己的理想,做一份自己喜愛的事業,擁有快樂和成就...

未來我們可以和性愛機器人結婚嗎?

你想體驗機器人性愛嗎?你想和性愛機器人結婚嗎?如果你想,機器人有拒絕你的權利嗎? 近日,第二屆“國際人類-機器人性愛研討會”大會在倫敦金史密斯大學落下帷幕。而...

全球最變態的十個地方

10.土耳其地下洞穴城市 變態指數:★★☆☆☆ 這是土耳其卡帕多西亞的一個著名景點,傳說是當年基督教徒們爲了躲避戰爭而在此修建。裏面曾住著20000人,...

科學家稱,人類死亡後意識將在另外一個宇宙中繼續存活

據英國《每日快報》報道,一位科學家兼理論家Robert Lanza博士宣稱,世界上並不存在人類死亡,死亡的只是身體。他認爲我們的意識借助我們體內的能量生存,而且...

《屏裏狐》片頭曲《我愛狐狸精》歌詞是什麽?

《我愛狐狸精》 - 劉馨棋   (電視劇《屏裏狐》主題曲)   作詞:金十三&李旦   作曲:劉嘉   狐狸精 狐狸仙   千年修...

 
 
 
  對于一個c/c++程序員來說,內存泄漏是一個常見的也是令人頭疼的問題。已經有許多技術被研究出來以應對這個問題,比如Smart Pointer,Garbage Collection等。Smart Pointer技術比較成熟,STL中已經包含支持Smart Pointer的class,但是它的使用似乎並不廣泛,而且它也不能解決所有的問題;Garbage Collection技術在Java中已經比較成熟,但是在c/c++領域的發展並不順暢,雖然很早就有人思考在C++中也加入GC的支持。現實世界就是這樣的,作爲一個c/c++程序員,內存泄漏是你心中永遠的痛。不過好在現在有許多工具能夠幫助我們驗證內存泄漏的存在,找出發生問題的代碼。   內存泄漏的定義   一般我們常說的內存泄漏是指堆內存的泄漏。堆內存是指程序從堆中分配的,大小任意的(內存塊的大小可以在程序運行期決定),使用完後必須顯示釋放的內存。應用程序一般使用malloc,realloc,new等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用free或delete釋放該內存塊,否則,這塊內存就不能被再次使用,我們就說這塊內存泄漏了。以下這段小程序演示了堆內存發生泄漏的情形: 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()返回零的時候,指針p指向的內存就不會被釋放。這是一種常見的發生內存泄漏的情形。程序在入口處分配內存,在出口處釋放內存,但是c函數可以在任何地方退出,所以一旦有某個出口處沒有釋放應該釋放的內存,就會發生內存泄漏。   廣義的說,內存泄漏不僅僅包含堆內存的泄漏,還包含系統資源的泄漏(resource leak),比如核心態HANDLE,GDI Object,SOCKET, Interface等,從根本上說這些由操作系統分配的對象也消耗內存,假如這些對象發生泄漏最終也會導致內存的泄漏。而且,某些對象消耗的是核心態內存,這些對象嚴重泄漏時會導致整個操作系統不穩定。所以相比之下,系統資源的泄漏比堆內存的泄漏更爲嚴重。   GDI Object的泄漏是一種常見的資源泄漏: 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()返回非零的時候,程序在退出前沒有把pOldBmp選回pDC中,這會導致pOldBmp指向的HBITMAP對象發生泄漏。這個程序假如長時間的運行,可能會導致整個系統花屏。這種問題在Win9x下比較輕易暴露出來,因爲Win9x的GDI堆比Win2k或NT的要小很多。   內存泄漏的發生方式:   以發生的方式來分類,內存泄漏可以分爲4類:   1. 常發性內存泄漏。發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。比如例二,假如Something()函數一直返回True,那麽pOldBmp指向的HBITMAP對象總是發生泄漏。   2. 偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。比如例二,假如Something()函數只有在特定環境下才返回True,那麽pOldBmp指向的HBITMAP對象並不總是發生泄漏。常發性和偶發性是相對的。對于特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。   3. 一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者由于算法上的缺陷,導致總會有一塊僅且一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,但是因爲這個類是一個Singleton,所以內存泄漏只會發生一次。另一個例子: char* g_lpszFileName = NULL; void SetFileName( const char* lpcszFileName ) {  if( g_lpszFileName ){   free( g_lpszFileName );  }  g_lpszFileName = strdup( lpcszFileName ); }   例三   假如程序在結束的時候沒有釋放g_lpszFileName指向的字符串,那麽,即使多次調用SetFileName(),總會有一塊內存,而且僅有一塊內存發生泄漏。   4. 隱式內存泄漏。程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這裏並沒有發生內存泄漏,因爲最終程序釋放了所有申請的內存。但是對于一個服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏爲隱式內存泄漏。舉一個例子: class Connection {  public:   Connection( SOCKET s);   ~Connection();   …  private:   SOCKET _socket;   … }; class ConnectionManager {  public:   ConnectionManager(){}   ~ConnectionManager(){    list::iterator it;    for( it = _connlist.begin(); it != _connlist.end(); ++it ){     delete (*it);    }    _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 _connlist; };   例四   假設在Client從Server端斷開後,Server並沒有呼叫OnClientDisconnected()函數,那麽代表那次連接的Connection對象就不會被及時的刪除(在Server程序退出的時候,所有Connection對象會在ConnectionManager的析構函數裏被刪除)。當不斷的有連接建立、斷開時隱式內存泄漏就發生了。   從用戶使用程序的角度來看,內存泄漏本身不會産生什麽危害,作爲一般的用戶,根本感覺不到內存泄漏的存在。真正有危害的是內存泄漏的堆積,這會最終消耗盡系統所有的內存。從這個角度來說,一次性內存泄漏並沒有什麽危害,因爲它不會堆積,而隱式內存泄漏危害性則非常大,因爲較之于常發性和偶發性內存泄漏它更難被檢測到。 Photoshop教程 數據結構 五筆輸入法專題 QQ病毒專題 共享上網專題 Google工具和服務專題 檢測內存泄漏   檢測內存泄漏的要害是要能截獲住對分配內存和釋放內存的函數的調用。截獲住這兩個函數,我們就能跟蹤每一塊內存的生命周期,比如,每當成功的分配一塊內存後,就把它的指針加入一個全局的list中;每當釋放一塊內存,再把它的指針從list中刪除。這樣,當程序結束的時候,list中剩余的指針就是指向那些沒有被釋放的內存。這裏只是簡單的描述了檢測內存泄漏的基本原理,具體的算法可以參見Steve Maguire的<<Writing Solid Code>>。   假如要檢測堆內存的泄漏,那麽需要截獲住malloc/realloc/free和new/delete就可以了(其實new/delete最終也是用malloc/free的,所以只要截獲前面一組即可)。對于其他的泄漏,可以采用類似的方法,截獲住相應的分配和釋放函數。比如,要檢測BSTR的泄漏,就需要截獲SysAllocString/SysFreeString;要檢測HMENU的泄漏,就需要截獲CreateMenu/ DestroyMenu。(有的資源的分配函數有多個,釋放函數只有一個,比如,SysAllocStringLen也可以用來分配BSTR,這時就需要截獲多個分配函數)   在Windows平台下,檢測內存泄漏的工具常用的一般有三種,MS C-Runtime Library內建的檢測功能;外挂式的檢測工具,諸如,Purify,BoundsChecker等;利用Windows NT自帶的Performance Monitor。這三種工具各有優缺點,MS C-Runtime Library雖然功能上較之外挂式的工具要弱,但是它是免費的;Performance Monitor雖然無法標示出發生問題的代碼,但是它能檢測出隱式的內存泄漏的存在,這是其他兩類工具無能爲力的地方。   以下我們具體討論這三種檢測工具:   VC下內存泄漏的檢測方法   用MFC開發的應用程序,在DEBUG版模式下編譯後,都會自動加入內存泄漏的檢測代碼。在程序結束後,假如發生了內存泄漏,在Debug窗口中會顯示出所有發生泄漏的內存塊的信息,以下兩行顯示了一塊被泄漏的內存塊的信息: 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   第一行顯示該內存塊由TestDlg.cpp文件,第70行代碼分配,地址在0x00881710,大小爲200字節,{59}是指調用內存分配函數的Request Order,關于它的具體信息可以參見MSDN中_CrtSetBreakAlloc()的幫助。第二行顯示該內存塊前16個字節的內容,尖括號內是以ASCII方式顯示,接著的是以16進制方式顯示。   一般大家都誤以爲這些內存泄漏的檢測功能是由MFC提供的,其實不然。MFC只是封裝和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入內存泄漏的檢測功能。MS C-Runtime Library在實現malloc/free,strdup等函數時已經內建了內存泄漏的檢測功能。   注重觀察一下由MFC Application Wizard生成的項目,在每一個cpp文件的頭部都有這樣一段宏定義: #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif   有了這樣的定義,在編譯DEBUG版時,出現在這個cpp文件中的所有new都被替換成DEBUG_NEW了。那麽DEBUG_NEW是什麽呢?DEBUG_NEW也是一個宏,以下摘自afx.h,1632行 #define DEBUG_NEW new(THIS_FILE, __LINE__)   所以假如有這樣一行代碼: char* p = new char[200];   經過宏替換就變成了: char* p = new( THIS_FILE, __LINE__)char[200];   根據C++的標准,對于以上的new的使用方法,編譯器會去找這樣定義的operator new: void* operator new(size_t, LPCSTR, int)   我們在afxmem.cpp 63行找到了一個這樣的operator new 的實現 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;  … }   第二個operator new函數比較長,爲了簡單期間,我只摘錄了部分。很顯然最後的內存分配還是通過_malloc_dbg函數實現的,這個函數屬于MS C-Runtime Library 的Debug Function。這個函數不但要求傳入內存的大小,另外還有文件名和行號兩個參數。文件名和行號就是用來記錄此次分配是由哪一段代碼造成的。假如這塊內存在程序結束之前沒有被釋放,那麽這些信息就會輸出到Debug窗口裏。   這裏順便提一下THIS_FILE,__FILE和__LINE__。__FILE__和__LINE__都是編譯器定義的宏。當碰到__FILE__時,編譯器會把__FILE__替換成一個字符串,這個字符串就是當前在編譯的文件的路徑名。當碰到__LINE__時,編譯器會把__LINE__替換成一個數字,這個數字就是當前這行代碼的行號。在DEBUG_NEW的定義中沒有直接使用__FILE__,而是用了THIS_FILE,其目的是爲了減小目標文件的大小。假設在某個cpp文件中有100處使用了new,假如直接使用__FILE__,那編譯器會産生100個常量字符串,這100個字符串都是飧?/SPAN>cpp文件的路徑名,顯然十分冗余。假如使用THIS_FILE,編譯器只會産生一個常量字符串,那100處new的調用使用的都是指向常量字符串的指針。   再次觀察一下由MFC Application Wizard生成的項目,我們會發現在cpp文件中只對new做了映射,假如你在程序中直接使用malloc函數分配內存,調用malloc的文件名和行號是不會被記錄下來的。假如這塊內存發生了泄漏,MS C-Runtime Library仍然能檢測到,但是當輸出這塊內存塊的信息,不會包含分配它的的文件名和行號。   要在非MFC程序中打開內存泄漏的檢測功能非常輕易,你只要在程序的入口處加入以下幾行代碼: int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ); tmpFlag = _CRTDBG_LEAK_CHECK_DF; _CrtSetDbgFlag( tmpFlag );   這樣,在程序結束的時候,也就是winmain,main或dllmain函數返回之後,假如還有內存塊沒有釋放,它們的信息會被打印到Debug窗口裏。   假如你試著創建了一個非MFC應用程序,而且在程序的入口處加入了以上代碼,並且故意在程序中不釋放某些內存塊,你會在Debug窗口裏看到以下的信息: {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   內存泄漏的確檢測到了,但是和上面MFC程序的例子相比,缺少了文件名和行號。對于一個比較大的程序,沒有這些信息,解決問題將變得十分困難。   爲了能夠知道泄漏的內存塊是在哪裏分配的,你需要實現類似MFC的映射功能,把new,maolloc等函數映射到_malloc_dbg函數上。這裏我不再贅述,你可以參考MFC的源代碼。   由于Debug Function實現在MS C-RuntimeLibrary中,所以它只能檢測到堆內存的泄漏,而且只限于malloc,realloc或strdup等分配的內存,而那些系統資源,比如HANDLE,GDI Object,或是不通過C-Runtime Library分配的內存,比如VARIANT,BSTR的泄漏,它是無法檢測到的,這是這種檢測法的一個重大的局限性。另外,爲了能記錄內存塊是在哪裏分配的,源代碼必須相應的配合,這在調試一些老的程序非常麻煩,究竟修改源代碼不是一件省心的事,這是這種檢測法的另一個局限性。   對于開發一個大型的程序,MS C-Runtime Library提供的檢測功能是遠遠不夠的。接下來我們就看看外挂式的檢測工具。我用的比較多的是BoundsChecker,一則因爲它的功能比較全面,更重要的是它的穩定性。這類工具假如不穩定,反而會忙裏添亂。到底是出自鼎鼎大名的NuMega,我用下來基本上沒有什麽大問題。 Photoshop教程 數據結構 五筆輸入法專題 QQ病毒專題 共享上網專題 Google工具和服務專題   使用BoundsChecker檢測內存泄漏:   BoundsChecker采用一種被稱爲 Code Injection的技術,來截獲對分配內存和釋放內存的函數的調用。簡單地說,當你的程序開始運行時,BoundsChecker的DLL被自動載入進程的地址空間(這可以通過system-level的Hook實現),然後它會修改進程中對內存分配和釋放的函數調用,讓這些調用首先轉入它的代碼,然後再執行原來的代碼。BoundsChecker在做這些動作的時,無須修改被調試程序的源代碼或工程配置文件,這使得使用它非常的簡便、直接。   這裏我們以malloc函數爲例,截獲其他的函數方法與此類似。   需要被截獲的函數可能在DLL中,也可能在程序的代碼裏。比如,假如靜態連結C-Runtime Library,那麽malloc函數的代碼會被連結到程序裏。爲了截獲住對這類函數的調用,BoundsChecker會動態修改這些函數的指令。   以下兩段彙編代碼,一段沒有BoundsChecker介入,另一段則有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: }   以下這一段代碼有BoundsChecker介入: 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的前三條彙編指令被替換成一條jmp指令,原來的三條指令被搬到地址01F41EC8處了。當程序進入malloc後先jmp到01F41EC8,執行原來的三條指令,然後就是BoundsChecker的天下了。大致上它會先記錄函數的返回地址(函數的返回地址在stack上,所以很輕易修改),然後把返回地址指向屬于BoundsChecker的代碼,接著跳到malloc函數原來的指令,也就是在00403c15的地方。當malloc函數結束的時候,由于返回地址被修改,它會返回到BoundsChecker的代碼中,此時BoundsChecker會記錄由malloc分配的內存的指針,然後再跳轉到到原來的返回地址去。   假如內存分配/釋放函數在DLL中,BoundsChecker則采用另一種方法來截獲對這些函數的調用。BoundsChecker通過修改程序的DLL Import Table讓table中的函數地址指向自己的地址,以達到截獲的目的。   截獲住這些分配和釋放函數,BoundsChecker就能記錄被分配的內存或資源的生命周期。接下來的問題是如何與源代碼相關,也就是說當BoundsChecker檢測到內存泄漏,它如何報告這塊內存塊是哪段代碼分配的。答案是調試信息(Debug Information)。當我們編譯一個Debug版的程序時,編譯器會把源代碼和二進制代碼之間的對應關系記錄下來,放到一個單獨的文件裏(.pdb)或者直接連結進目標程序,通過直接讀取調試信息就能得到分配某塊內存的源代碼在哪個文件,哪一行上。使用Code Injection和Debug Information,使BoundsChecker不但能記錄呼叫分配函數的源代碼的位置,而且還能記錄分配時的Call Stack,以及Call Stack上的函數的源代碼位置。這在使用像MFC這樣的類庫時非常有用,以下我用一個例子來說明: 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()中分配的。假設的你的程序有許多地方使用了CMenu的CreatePopupMenu()函數,如CMenu::CreatePopupMenu()造成的,你依然無法確認問題的根結到底在哪裏,在ShowXItemMenu()中還是在ShowYItemMenu()中,或者還有其它的地方也使用了CreatePopupMenu()?有了Call Stack的信息,問題就輕易了。BoundsChecker會如下報告泄漏的HMENU的信息: Function File Line CMenu::CreatePopupMenu E:\8168\vc98\mfc\mfc\include\afxwin1.inl 1009 ShowYItemMenu E:\testmemleak\mytest.cpp 100   這裏省略了其他的函數調用   如此,我們很輕易找到發生問題的函數是ShowYItemMenu()。當使用MFC之類的類庫編程時,大部分的API調用都被封裝在類庫的class裏,有了Call Stack信息,我們就可以非常輕易的追蹤到真正發生泄漏的代碼。   記錄Call Stack信息會使程序的運行變得非常慢,因此默認情況下BoundsChecker不會記錄Call Stack信息。可以按照以下的步驟打開記錄Call Stack信息的選項開關:   1. 打開菜單:BoundsCheckerSetting…   2. 在Error Detection頁中,在Error Detection Scheme的List中選擇Custom   3. 在Category的Combox中選擇 Pointer and leak error check   4. 鈎上Report Call Stack複選框   5. 點擊Ok   基于Code Injection,BoundsChecker還提供了API Parameter的校驗功能,memory over run等功能。這些功能對于程序的開發都非常有益。由于這些內容不屬于本文的主題,所以不在此詳述了。   盡管BoundsChecker的功能如此強大,但是面對隱式內存泄漏仍然顯得蒼白無力。所以接下來我們看看如何用Performance Monitor檢測內存泄漏。   使用Performance Monitor檢測內存泄漏   NT的內核在設計過程中已經加入了系統監視功能,比如CPU的使用率,內存的使用情況,I/O操作的頻繁度等都作爲一個個Counter,應用程序可以通過讀取這些Counter了解整個系統的或者某個進程的運行狀況。Performance Monitor就是這樣一個應用程序。   爲了檢測內存泄漏,我們一般可以監視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卻是跳躍式增加的。   監視這些Counter可以讓我們了解進程使用內存的情況,假如發生了泄漏,即使是隱式內存泄漏,這些Counter的值也會持續增加。但是,我們知道有問題卻不知道哪裏有問題,所以一般使用Performance Monitor來驗證是否有內存泄漏,而使用BoundsChecker來找到和解決。   當Performance Monitor顯示有內存泄漏,而BoundsChecker卻無法檢測到,這時有兩種可能:第一種,發生了偶發性內存泄漏。這時你要確保使用Performance Monitor和使用BoundsChecker時,程序的運行環境和操作方法是一致的。第二種,發生了隱式的內存泄漏。這時你要重新審查程序的設計,然後仔細研究Performance Monitor記錄的Counter的值的變化圖,分析其中的變化和程序運行邏輯的關系,找到一些可能的原因。這是一個痛苦的過程,布滿了假設、猜想、驗證、失敗,但這也是一個積累經驗的絕好機會。   總結   內存泄漏是個大而複雜的問題,即使是Java和.Net這樣有Gabarge Collection機制的環境,也存在著泄漏的可能,比如隱式內存泄漏。由于篇幅和能力的限制,本文只能對這個主題做一個粗淺的研究。其他的問題,比如多模塊下的泄漏檢測,如何在程序運行時對內存使用情況進行分析等等,都是可以深入研究的題目。假如您有什麽想法,建議或發現了某些錯誤,歡迎和我交流。
󰈣󰈤
 
 
 
  免責聲明:本文僅代表作者個人觀點,與王朝網路無關。王朝網路登載此文出於傳遞更多信息之目的,並不意味著贊同其觀點或證實其描述,其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,並請自行核實相關內容。
 
 
陽光靓麗的模特兒(8)
陽光靓麗的模特兒(7)
陽光靓麗的模特兒(6)
陽光靓麗的模特兒(5)
秋-印象
德慶盤龍峽 一
松江印象之三
雲之南(寬幅)
 
>>返回首頁<<
 
 
 
 熱帖排行
 
 
 
 
© 2005- 王朝網路 版權所有