• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 27  文章 - 88  trackbacks - 0
            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            常用鏈接

            留言簿(4)

            文章分類(3)

            文章檔案(3)

            搜索

            •  

            積分與排名

            • 積分 - 130159
            • 排名 - 198

            最新評論

            閱讀排行榜

            評論排行榜

            對于一個c/c++程序員來說,內(nèi)存泄漏是一個常見的也是令人頭疼的問題。已經(jīng)有許多技術(shù)被研究出來以應(yīng)對這個問題,比如Smart Pointer,Garbage Collection等。Smart Pointer技術(shù)比較成熟,STL中已經(jīng)包含支持Smart Pointer的class,但是它的使用似乎并不廣泛,而且它也不能解決所有的問題;Garbage Collection技術(shù)在Java中已經(jīng)比較成熟,但是在c/c++領(lǐng)域的發(fā)展并不順暢,雖然很早就有人思考在C++中也加入GC的支持。現(xiàn)實世界就是這樣的,作為一個c/c++程序員,內(nèi)存泄漏是你心中永遠的痛。不過好在現(xiàn)在有許多工具能夠幫助我們驗證內(nèi)存泄漏的存在,找出發(fā)生問題的代碼。


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

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


              當函數(shù)GetStringFrom()返回零的時候,指針p指向的內(nèi)存就不會被釋放。這是一種常見的發(fā)生內(nèi)存泄漏的情形。程序在入口處分配內(nèi)存,在出口處釋放內(nèi)存,但是c函數(shù)可以在任何地方退出,所以一旦有某個出口處沒有釋放應(yīng)該釋放的內(nèi)存,就會發(fā)生內(nèi)存泄漏。
            廣義的說,內(nèi)存泄漏不僅僅包含堆內(nèi)存的泄漏,還包含系統(tǒng)資源的泄漏(resource leak),比如核心態(tài)HANDLE,GDI Object,SOCKET,Interface等,從根本上說這些由操作系統(tǒng)分配的對象也消耗內(nèi)存,如果這些對象發(fā)生泄漏最終也會導(dǎo)致內(nèi)存的泄漏。而且,某些對象消耗的是核心態(tài)內(nèi)存,這些對象嚴重泄漏時會導(dǎo)致整個操作系統(tǒng)不穩(wěn)定。所以相比之下,系統(tǒng)資源的泄漏比堆內(nèi)存的泄漏更為嚴重。
              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;
            }

              當函數(shù)Something()返回非零的時候,程序在退出前沒有把pOldBmp選回pDC中,這會導(dǎo)致pOldBmp指向的HBITMAP對象發(fā)生泄漏。這個程序如果長時間的運行,可能會導(dǎo)致整個系統(tǒng)花屏。這種問題在Win9x下比較容易暴露出來,因為Win9x的GDI堆比Win2k或NT的要小很多。
            內(nèi)存泄漏的發(fā)生方式:
              以發(fā)生的方式來分類,內(nèi)存泄漏可以分為4類:
              1. 常發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼會被多次執(zhí)行到,每次被執(zhí)行的時候都會導(dǎo)致一塊內(nèi)存泄漏。比如例二,如果Something()函數(shù)一直返回True,那么pOldBmp指向的HBITMAP對象總是發(fā)生泄漏。
              2. 偶發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過程下才會發(fā)生。比如例二,如果Something()函數(shù)只有在特定環(huán)境下才返回True,那么pOldBmp指向的HBITMAP對象并不總是發(fā)生泄漏。常發(fā)性和偶發(fā)性是相對的。對于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。所以測試環(huán)境和測試方法對檢測內(nèi)存泄漏至關(guān)重要。
              3. 一次性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只會被執(zhí)行一次,或者由于算法上的缺陷,導(dǎo)致總會有一塊僅且一塊內(nèi)存發(fā)生泄漏。比如,在類的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒有釋放該內(nèi)存,但是因為這個類是一個Singleton,所以內(nèi)存泄漏只會發(fā)生一次。另一個例子:

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


              如果程序在結(jié)束的時候沒有釋放g_lpszFileName指向的字符串,那么,即使多次調(diào)用SetFileName(),總會有一塊內(nèi)存,而且僅有一塊內(nèi)存發(fā)生泄漏。
              4. 隱式內(nèi)存泄漏。程序在運行過程中不停的分配內(nèi)存,但是直到結(jié)束的時候才釋放內(nèi)存。嚴格的說這里并沒有發(fā)生內(nèi)存泄漏,因為最終程序釋放了所有申請的內(nèi)存。但是對于一個服務(wù)器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏。舉一個例子:

            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 (*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<Connection*> _connlist;
            };


              假設(shè)在Client從Server端斷開后,Server并沒有呼叫OnClientDisconnected()函數(shù),那么代表那次連接的Connection對象就不會被及時的刪除(在Server程序退出的時候,所有Connection對象會在ConnectionManager的析構(gòu)函數(shù)里被刪除)。當不斷的有連接建立、斷開時隱式內(nèi)存泄漏就發(fā)生了。
              從用戶使用程序的角度來看,內(nèi)存泄漏本身不會產(chǎn)生什么危害,作為一般的用戶,根本感覺不到內(nèi)存泄漏的存在。真正有危害的是內(nèi)存泄漏的堆積,這會最終消耗盡系統(tǒng)所有的內(nèi)存。從這個角度來說,一次性內(nèi)存泄漏并沒有什么危害,因為它不會堆積,而隱式內(nèi)存泄漏危害性則非常大,因為較之于常發(fā)性和偶發(fā)性內(nèi)存泄漏它更難被檢測到。

            檢測內(nèi)存泄漏:

              檢測內(nèi)存泄漏的關(guān)鍵是要能截獲住對分配內(nèi)存和釋放內(nèi)存的函數(shù)的調(diào)用。截獲住這兩個函數(shù),我們就能跟蹤每一塊內(nèi)存的生命周期,比如,每當成功的分配一塊內(nèi)存后,就把它的指針加入一個全局的list中;每當釋放一塊內(nèi)存,再把它的指針從list中刪除。這樣,當程序結(jié)束的時候,list中剩余的指針就是指向那些沒有被釋放的內(nèi)存。這里只是簡單的描述了檢測內(nèi)存泄漏的基本原理,詳細的算法可以參見Steve Maguire的<<Writing Solid Code>>。
              如果要檢測堆內(nèi)存的泄漏,那么需要截獲住malloc/realloc/free和new/delete就可以了(其實new/delete最終也是用malloc/free的,所以只要截獲前面一組即可)。對于其他的泄漏,可以采用類似的方法,截獲住相應(yīng)的分配和釋放函數(shù)。比如,要檢測BSTR的泄漏,就需要截獲SysAllocString/SysFreeString;要檢測HMENU的泄漏,就需要截獲CreateMenu/ DestroyMenu。(有的資源的分配函數(shù)有多個,釋放函數(shù)只有一個,比如,SysAllocStringLen也可以用來分配BSTR,這時就需要截獲多個分配函數(shù))
              在Windows平臺下,檢測內(nèi)存泄漏的工具常用的一般有三種,MS C-Runtime Library內(nèi)建的檢測功能;外掛式的檢測工具,諸如,Purify,BoundsChecker等;利用Windows NT自帶的Performance Monitor。這三種工具各有優(yōu)缺點,MS C-Runtime Library雖然功能上較之外掛式的工具要弱,但是它是免費的;Performance Monitor雖然無法標示出發(fā)生問題的代碼,但是它能檢測出隱式的內(nèi)存泄漏的存在,這是其他兩類工具無能為力的地方。
              以下我們詳細討論這三種檢測工具:


            VC下內(nèi)存泄漏的檢測方法

              用MFC開發(fā)的應(yīng)用程序,在DEBUG版模式下編譯后,都會自動加入內(nèi)存泄漏的檢測代碼。在程序結(jié)束后,如果發(fā)生了內(nèi)存泄漏,在Debug窗口中會顯示出所有發(fā)生泄漏的內(nèi)存塊的信息,以下兩行顯示了一塊被泄漏的內(nèi)存塊的信息:
            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
              第一行顯示該內(nèi)存塊由TestDlg.cpp文件,第70行代碼分配,地址在0x00881710,大小為200字節(jié),{59}是指調(diào)用內(nèi)存分配函數(shù)的Request Order,關(guān)于它的詳細信息可以參見MSDN中_CrtSetBreakAlloc()的幫助。第二行顯示該內(nèi)存塊前16個字節(jié)的內(nèi)容,尖括號內(nèi)是以ASCII方式顯示,接著的是以16進制方式顯示。
              一般大家都誤以為這些內(nèi)存泄漏的檢測功能是由MFC提供的,其實不然。MFC只是封裝和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入內(nèi)存泄漏的檢測功能。MS C-Runtime Library在實現(xiàn)malloc/free,strdup等函數(shù)時已經(jīng)內(nèi)建了內(nèi)存泄漏的檢測功能。
              注意觀察一下由MFC Application Wizard生成的項目,在每一個cpp文件的頭部都有這樣一段宏定義:
            #ifdef _DEBUG
            #define new DEBUG_NEW
            #undef THIS_FILE
            static char THIS_FILE[] = __FILE__;
            #endif
              有了這樣的定義,在編譯DEBUG版時,出現(xiàn)在這個cpp文件中的所有new都被替換成DEBUG_NEW了。那么DEBUG_NEW是什么呢?DEBUG_NEW也是一個宏,以下摘自afx.h,1632行
            #define DEBUG_NEW new(THIS_FILE, __LINE__)
            所以如果有這樣一行代碼:
            char* p = new char[200];
            經(jīng)過宏替換就變成了:
            char* p = new( THIS_FILE, __LINE__)char[200];
            根據(jù)C++的標準,對于以上的new的使用方法,編譯器會去找這樣定義的operator new:
            void* operator new(size_t, LPCSTR, int)
            我們在afxmem.cpp 63行找到了一個這樣的operator new 的實現(xiàn)

            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函數(shù)比較長,為了簡單期間,我只摘錄了部分。很顯然最后的內(nèi)存分配還是通過_malloc_dbg函數(shù)實現(xiàn)的,這個函數(shù)屬于MS C-Runtime Library 的Debug Function。這個函數(shù)不但要求傳入內(nèi)存的大小,另外還有文件名和行號兩個參數(shù)。文件名和行號就是用來記錄此次分配是由哪一段代碼造成的。如果這塊內(nèi)存在程序結(jié)束之前沒有被釋放,那么這些信息就會輸出到Debug窗口里。
              這里順便提一下THIS_FILE,__FILE和__LINE__。__FILE__和__LINE__都是編譯器定義的宏。當碰到__FILE__時,編譯器會把__FILE__替換成一個字符串,這個字符串就是當前在編譯的文件的路徑名。當碰到__LINE__時,編譯器會把__LINE__替換成一個數(shù)字,這個數(shù)字就是當前這行代碼的行號。在DEBUG_NEW的定義中沒有直接使用__FILE__,而是用了THIS_FILE,其目的是為了減小目標文件的大小。假設(shè)在某個cpp文件中有100處使用了new,如果直接使用__FILE__,那編譯器會產(chǎn)生100個常量字符串,這100個字符串都是這個cpp文件的路徑名,顯然十分冗余。如果使用THIS_FILE,編譯器只會產(chǎn)生一個常量字符串,那100處new的調(diào)用使用的都是指向常量字符串的指針。
              再次觀察一下由MFC Application Wizard生成的項目,我們會發(fā)現(xiàn)在cpp文件中只對new做了映射,如果你在程序中直接使用malloc函數(shù)分配內(nèi)存,調(diào)用malloc的文件名和行號是不會被記錄下來的。如果這塊內(nèi)存發(fā)生了泄漏,MS C-Runtime Library仍然能檢測到,但是當輸出這塊內(nèi)存塊的信息,不會包含分配它的的文件名和行號。
            要在非MFC程序中打開內(nèi)存泄漏的檢測功能非常容易,你只要在程序的入口處加入以下幾行代碼:

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


              這樣,在程序結(jié)束的時候,也就是winmain,main或dllmain函數(shù)返回之后,如果還有內(nèi)存塊沒有釋放,它們的信息會被打印到Debug窗口里。
            如果你試著創(chuàng)建了一個非MFC應(yīng)用程序,而且在程序的入口處加入了以上代碼,并且故意在程序中不釋放某些內(nèi)存塊,你會在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
              內(nèi)存泄漏的確檢測到了,但是和上面MFC程序的例子相比,缺少了文件名和行號。對于一個比較大的程序,沒有這些信息,解決問題將變得十分困難。
              為了能夠知道泄漏的內(nèi)存塊是在哪里分配的,你需要實現(xiàn)類似MFC的映射功能,把new,maolloc等函數(shù)映射到_malloc_dbg函數(shù)上。這里我不再贅述,你可以參考MFC的源代碼。
              由于Debug Function實現(xiàn)在MS C-RuntimeLibrary中,所以它只能檢測到堆內(nèi)存的泄漏,而且只限于malloc,realloc或strdup等分配的內(nèi)存,而那些系統(tǒng)資源,比如HANDLE,GDI Object,或是不通過C-Runtime Library分配的內(nèi)存,比如VARIANT,BSTR的泄漏,它是無法檢測到的,這是這種檢測法的一個重大的局限性。另外,為了能記錄內(nèi)存塊是在哪里分配的,源代碼必須相應(yīng)的配合,這在調(diào)試一些老的程序非常麻煩,畢竟修改源代碼不是一件省心的事,這是這種檢測法的另一個局限性。
              對于開發(fā)一個大型的程序,MS C-Runtime Library提供的檢測功能是遠遠不夠的。接下來我們就看看外掛式的檢測工具。我用的比較多的是BoundsChecker,一則因為它的功能比較全面,更重要的是它的穩(wěn)定性。這類工具如果不穩(wěn)定,反而會忙里添亂。到底是出自鼎鼎大名的NuMega,我用下來基本上沒有什么大問題。

            使用BoundsChecker檢測內(nèi)存泄漏:

              BoundsChecker采用一種被稱為Code Injection的技術(shù),來截獲對分配內(nèi)存和釋放內(nèi)存的函數(shù)的調(diào)用。簡單地說,當你的程序開始運行時,BoundsChecker的DLL被自動載入進程的地址空間(這可以通過system-level的Hook實現(xiàn)),然后它會修改進程中對內(nèi)存分配和釋放的函數(shù)調(diào)用,讓這些調(diào)用首先轉(zhuǎn)入它的代碼,然后再執(zhí)行原來的代碼。BoundsChecker在做這些動作的時,無須修改被調(diào)試程序的源代碼或工程配置文件,這使得使用它非常的簡便、直接。
              這里我們以malloc函數(shù)為例,截獲其他的函數(shù)方法與此類似。
              需要被截獲的函數(shù)可能在DLL中,也可能在程序的代碼里。比如,如果靜態(tài)連結(jié)C-Runtime Library,那么malloc函數(shù)的代碼會被連結(jié)到程序里。為了截獲住對這類函數(shù)的調(diào)用,BoundsChecker會動態(tài)修改這些函數(shù)的指令。
              以下兩段匯編代碼,一段沒有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介入后,函數(shù)malloc的前三條匯編指令被替換成一條jmp指令,原來的三條指令被搬到地址01F41EC8處了。當程序進入malloc后先jmp到01F41EC8,執(zhí)行原來的三條指令,然后就是BoundsChecker的天下了。大致上它會先記錄函數(shù)的返回地址(函數(shù)的返回地址在stack上,所以很容易修改),然后把返回地址指向?qū)儆贐oundsChecker的代碼,接著跳到malloc函數(shù)原來的指令,也就是在00403c15的地方。當malloc函數(shù)結(jié)束的時候,由于返回地址被修改,它會返回到BoundsChecker的代碼中,此時BoundsChecker會記錄由malloc分配的內(nèi)存的指針,然后再跳轉(zhuǎn)到到原來的返回地址去。
              如果內(nèi)存分配/釋放函數(shù)在DLL中,BoundsChecker則采用另一種方法來截獲對這些函數(shù)的調(diào)用。BoundsChecker通過修改程序的DLL Import Table讓table中的函數(shù)地址指向自己的地址,以達到截獲的目的。關(guān)于如何攔截Windows的系統(tǒng)函數(shù),《程序員》雜志2002年8期,《API鉤子揭密(下)》,對修改導(dǎo)入地址表做了概要的描述。我就不再贅述。
              截獲住這些分配和釋放函數(shù),BoundsChecker就能記錄被分配的內(nèi)存或資源的生命周期。接下來的問題是如何與源代碼相關(guān),也就是說當BoundsChecker檢測到內(nèi)存泄漏,它如何報告這塊內(nèi)存塊是哪段代碼分配的。答案是調(diào)試信息(Debug Information)。當我們編譯一個Debug版的程序時,編譯器會把源代碼和二進制代碼之間的對應(yīng)關(guān)系記錄下來,放到一個單獨的文件里(.pdb)或者直接連結(jié)進目標程序中。有了這些信息,調(diào)試器才能完成斷點設(shè)置,單步執(zhí)行,查看變量等功能。BoundsChecker支持多種調(diào)試信息格式,它通過直接讀取調(diào)試信息就能得到分配某塊內(nèi)存的源代碼在哪個文件,哪一行上。使用Code Injection和Debug Information,使BoundsChecker不但能記錄呼叫分配函數(shù)的源代碼的位置,而且還能記錄分配時的Call Stack,以及Call Stack上的函數(shù)的源代碼位置。這在使用像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();

            }


              當調(diào)用ShowYItemMenu()時,我們故意造成HMENU的泄漏。但是,對于BoundsChecker來說被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假設(shè)的你的程序有許多地方使用了CMenu的CreatePopupMenu()函數(shù),如果只是告訴你泄漏是由CMenu::CreatePopupMenu()造成的,你依然無法確認問題的根結(jié)到底在哪里,在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

             

              這里省略了其他的函數(shù)調(diào)用

              如此,我們很容易找到發(fā)生問題的函數(shù)是ShowYItemMenu()。當使用MFC之類的類庫編程時,大部分的API調(diào)用都被封裝在類庫的class里,有了Call Stack信息,我們就可以非常容易的追蹤到真正發(fā)生泄漏的代碼。
              記錄Call Stack信息會使程序的運行變得非常慢,因此默認情況下BoundsChecker不會記錄Call Stack信息。可以按照以下的步驟打開記錄Call Stack信息的選項開關(guān):
            1. 打開菜單:BoundsChecker|Setting…
            2. 在Error Detection頁中,在Error Detection Scheme的List中選擇Custom
            3. 在Category的Combox中選擇Pointer and leak error check
            4. 鉤上Report Call Stack復(fù)選框
            5. 點擊Ok
              基于Code Injection,BoundsChecker還提供了API Parameter的校驗功能,memory over run等功能。這些功能對于程序的開發(fā)都非常有益。由于這些內(nèi)容不屬于本文的主題,所以不在此詳述了。
              盡管BoundsChecker的功能如此強大,但是面對隱式內(nèi)存泄漏仍然顯得蒼白無力。所以接下來我們看看如何用Performance Monitor檢測內(nèi)存泄漏。


            使用Performance Monitor檢測內(nèi)存泄漏

              NT的內(nèi)核在設(shè)計過程中已經(jīng)加入了系統(tǒng)監(jiān)視功能,比如CPU的使用率,內(nèi)存的使用情況,I/O操作的頻繁度等都作為一個個Counter,應(yīng)用程序可以通過讀取這些Counter了解整個系統(tǒng)的或者某個進程的運行狀況。Performance Monitor就是這樣一個應(yīng)用程序。
              為了檢測內(nèi)存泄漏,我們一般可以監(jiān)視Process對象的Handle Count,Virutal Bytes 和Working Set三個Counter。Handle Count記錄了進程當前打開的HANDLE的個數(shù),監(jiān)視這個Counter有助于我們發(fā)現(xiàn)程序是否有Handle泄漏;Virtual Bytes記錄了該進程當前在虛地址空間上使用的虛擬內(nèi)存的大小,NT的內(nèi)存分配采用了兩步走的方法,首先,在虛地址空間上保留一段空間,這時操作系統(tǒng)并沒有分配物理內(nèi)存,只是保留了一段地址。然后,再提交這段空間,這時操作系統(tǒng)才會分配物理內(nèi)存。所以,Virtual Bytes一般總大于程序的Working Set。監(jiān)視Virutal Bytes可以幫助我們發(fā)現(xiàn)一些系統(tǒng)底層的問題; Working Set記錄了操作系統(tǒng)為進程已提交的內(nèi)存的總量,這個值和程序申請的內(nèi)存總量存在密切的關(guān)系,如果程序存在內(nèi)存的泄漏這個值會持續(xù)增加,但是Virtual Bytes卻是跳躍式增加的。
              監(jiān)視這些Counter可以讓我們了解進程使用內(nèi)存的情況,如果發(fā)生了泄漏,即使是隱式內(nèi)存泄漏,這些Counter的值也會持續(xù)增加。但是,我們知道有問題卻不知道哪里有問題,所以一般使用Performance Monitor來驗證是否有內(nèi)存泄漏,而使用BoundsChecker來找到和解決問題。
              當Performance Monitor顯示有內(nèi)存泄漏,而BoundsChecker卻無法檢測到,這時有兩種可能:第一種,發(fā)生了偶發(fā)性內(nèi)存泄漏。這時你要確保使用Performance Monitor和使用BoundsChecker時,程序的運行環(huán)境和操作方法是一致的。第二種,發(fā)生了隱式的內(nèi)存泄漏。這時你要重新審查程序的設(shè)計,然后仔細研究Performance Monitor記錄的Counter的值的變化圖,分析其中的變化和程序運行邏輯的關(guān)系,找到一些可能的原因。這是一個痛苦的過程,充滿了假設(shè)、猜想、驗證、失敗,但這也是一個積累經(jīng)驗的絕好機會。

            posted on 2008-04-03 22:28 Macaulish 閱讀(492) 評論(0)  編輯 收藏 引用 所屬分類: c/c++
            国产成人久久精品激情| 无码人妻精品一区二区三区久久| 亚洲精品乱码久久久久久自慰| 亚洲国产精品成人久久蜜臀| 狠狠色婷婷久久综合频道日韩 | 亚洲成色WWW久久网站| 国产69精品久久久久9999APGF | 久久久一本精品99久久精品88 | 久久国产香蕉一区精品| 久久九九久精品国产免费直播| 久久久久久国产精品无码超碰| 国产精品成人久久久久三级午夜电影 | 欧美激情精品久久久久| 精品久久久久久久国产潘金莲| 久久精品国产亚洲av高清漫画| 青青草原综合久久大伊人精品| 久久久久久午夜精品| 一本色道久久88加勒比—综合| 久久精品国产亚洲AV忘忧草18| 久久精品国产福利国产秒| 国产精品中文久久久久久久| 99久久亚洲综合精品网站| 午夜精品久久久久久久| 一本大道久久香蕉成人网| 久久国产亚洲精品麻豆| 久久久久成人精品无码中文字幕| 久久久久久A亚洲欧洲AV冫 | 人妻少妇精品久久| 999久久久国产精品| 国产精品9999久久久久| 久久精品亚洲中文字幕无码麻豆| 欧洲性大片xxxxx久久久| 精品无码久久久久久国产| 国产综合久久久久| 国产高潮国产高潮久久久| 久久精品中文騷妇女内射| 人妻精品久久久久中文字幕69| 亚洲精品蜜桃久久久久久| 中文国产成人精品久久不卡| 99久久精品免费看国产一区二区三区 | 国产精品久久久久…|