• <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>
            面對現(xiàn)實,超越自己
            逆水行舟,不進(jìn)則退
            posts - 269,comments - 32,trackbacks - 0
            之所以撰寫這篇文章是因為前段時間花費(fèi)了很大的精力在已經(jīng)成熟的代碼上再去處理memory leak問題。寫此的目的是希望我們應(yīng)該養(yǎng)成良好的編碼習(xí)慣,盡可能的避免這樣的問題,因為當(dāng)你對著一大片的代碼再去處理此類的問題,此時無疑增加了解決的成本和難度。準(zhǔn)確的說屬于補(bǔ)救措施了。
            1. 什么是內(nèi)存泄漏(memory leak)?

             指由于疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。內(nèi)存泄漏并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)計錯誤,失去了對該段內(nèi)存的控制,因而造成了內(nèi)存的浪費(fèi)。 

            A memory leak is a particular type of unintentional memory consumption by a computer program where the program fails to release memory when no longer needed. This condition is normally the result of a bug in a program that prevents it from freeing up memory that it no longer needs.This term has the potential to be confusing, since memory is not physically lost from the computer. Rather, memory is allocated to a program, and that program subsequently loses the ability to access it due to program logic flaws. 

            2. 對于C和C++這種沒有Garbage Collection 的語言來講,我們主要關(guān)注兩種類型的內(nèi)存泄漏:

               堆內(nèi)存泄漏(Heap leak)。對內(nèi)存指的是程序運(yùn)行中根據(jù)需要分配通過malloc,realloc new等從堆中分配的一塊內(nèi)存,再是完成后必須通過調(diào)用對應(yīng)的 free或者delete 刪掉。如果程序的設(shè)計的錯誤導(dǎo)致這部分內(nèi)存沒有被釋放,那么此后這塊內(nèi)存將不會被使用,就會產(chǎn)生Heap Leak. 

              系統(tǒng)資源泄露(Resource Leak).主要指程序使用系統(tǒng)分配的資源比如 Bitmap,handle ,SOCKET等沒有使用相應(yīng)的函數(shù)釋放掉,導(dǎo)致系統(tǒng)資源的浪費(fèi),嚴(yán)重可導(dǎo)致系統(tǒng)效能降低,系統(tǒng)運(yùn)行不穩(wěn)定。  

            3. 如何解決內(nèi)存泄露?

            內(nèi)存泄露的問題其困難在于1.編譯器不能發(fā)現(xiàn)這些問題。2.運(yùn)行時才能捕獲到這些錯誤,這些錯誤沒有明顯的癥狀,時隱時現(xiàn)。3.對于手機(jī)等終端開發(fā)用戶來說,尤為困難。下面從三個方面來解決內(nèi)存泄露:

            第一,良好的編碼習(xí)慣,盡量在涉及內(nèi)存的程序段,檢測出內(nèi)存泄露。當(dāng)程式穩(wěn)定之后,在來檢測內(nèi)存泄露時,無疑增加了排除的困難和復(fù)雜度。

            使用了內(nèi)存分配的函數(shù),要記得要使用其想用的函數(shù)釋放掉,一旦使用完畢。

            Heap memory:

            malloc\realloc ------  free

            new \new[] ----------  delete \delete[]

            GlobalAlloc------------GlobalFree 

            要特別注意數(shù)組對象的內(nèi)存泄漏

                 MyPointEX *pointArray =new MyPointEX [100];

                  其刪除形式為:

                 delete []pointArray 

            Resource Leak :對于系統(tǒng)資源使用之前要仔細(xì)看起使用方法,防止錯誤使用或者忘記釋放掉系統(tǒng)資源。

            我們看MSDN上一個創(chuàng)建字體的例子:
            示例代碼
            如果使用完成時候忘記釋放字體,就造成了資源泄漏。 

               對于基于引用計數(shù)的系統(tǒng)對象尤其要注意,因為只有其引用計數(shù)為0時,該對象才能正確被刪除。而其使用過程中有其生成的新的系統(tǒng)資源,使用完畢后,如果沒有及時刪除,都會影響其引用計數(shù)。
            示例代碼

            DNS_Release(pMe->m_pDns);//當(dāng)程序運(yùn)行到此時,其返回值不是0,是1,其含義是程序已經(jīng)產(chǎn)生內(nèi)存泄露了,系統(tǒng)已經(jīng)有一個由DNS所產(chǎn)生的內(nèi)核對象沒有釋放,而當(dāng)這段代碼多次執(zhí)行之后,內(nèi)存泄露將不斷增加……..

            m_pDns=NULL;

              }

            看起來很不直觀,仔細(xì)分析就會發(fā)現(xiàn),對象pDnsResponse是從m_pDns產(chǎn)生新的object,所以m_pDns的引用計數(shù)會增加,因此在使用完pDnsResponse,應(yīng)該release 該對象使其引用計數(shù)恢復(fù)正常。
             
            對于資源,也可使用RAII,RAII(Resource acquisition is initialization)資源獲取即初始化,它是一項很簡單的技術(shù),利用C++對象生命周期的概念來控制程序的資源,例如內(nèi)存,文件句柄,網(wǎng)絡(luò)連接以及審計追蹤(audit trail)等.RAII的基本技術(shù)原理很簡單.若希望保持對某個重要資源的跟蹤,那么創(chuàng)建一個對象,并將資源的生命周期和對象的生命周期相關(guān)聯(lián).如此一來,就可以利用C++復(fù)雜老練的對象管理設(shè)施來管理資源.(有待完善) 

            例2: 

            Struct ITypeface *pTypeface;

            if (pTypeface)

            {

            IANY_CreateInstance(g_pApplet->m_pIShell,AEECLSID_BTFETypeface,void**)& Typeface);

            } 

            接下來我們就可以從這個接口上面創(chuàng)建字體,比如

            IHFont **pihf=NULL;

               ITypeface_NewFontFromFile(ITypeface,……,&pihf).

               ITypeface_NewFontFrommemory(ITypeface,……..,&pihf)

               ITypeface_NewFontFromClassID(IType,……,&pihf)

             

               但是要切記,這些字體在使用完成后一定要release掉,否則最后 iTypeface的引用計數(shù)就是你最后沒有刪除掉的字體的個數(shù)。 

            第二,重載  new 和 delete。這也是大家編碼過程中常常使用的方法。

            下面給出簡單的sample來說明。
            示例代碼

            其主要思路是將分配的內(nèi)存以鏈表的形式自行管理,使用完畢之后從鏈表中刪除,程序結(jié)束時可檢查改鏈表,其中記錄了內(nèi)存泄露的文件,所在文件的行數(shù)以及泄露的大小哦。
            第三,Boost 中的smart pointer(待完善,結(jié)合大家的建議)
            第四,一些常見的工具插件,詳見我的Blog中相關(guān)文章。

            4. 由內(nèi)存泄露引出內(nèi)存溢出話題:

            所謂內(nèi)存溢出就是你要求分配的內(nèi)存超出了系統(tǒng)能給你的,系統(tǒng)不能滿足需求,于是會產(chǎn)生內(nèi)存溢出的問題。

            常見的溢出主要有:

            內(nèi)存分配未成功,卻使用了它。 常用解決辦法是,在使用內(nèi)存之前檢查指針是否為NULL。如果指針p 是函數(shù)的參數(shù),那么在函數(shù)的入口處用assert(p!=NULL)進(jìn)行檢查。如果是用malloc 或new 來申請內(nèi)存,應(yīng)該用if(p==NULL)或if(p!=NULL)進(jìn)行防錯處理。

            內(nèi)存分配雖然成功,但是尚未初始化就引用它。 內(nèi)存分配成功并且已經(jīng)初始化,但操作越過了內(nèi)存的邊界。 例如在使用數(shù)組時經(jīng)常發(fā)生下標(biāo)“多1”或者“少1”的操作。特別是在for 循環(huán)語句中,循環(huán)次數(shù)很容易搞錯,導(dǎo)致數(shù)組操作越界。

            使用free 或delete 釋放了內(nèi)存后,沒有將指針設(shè)置為NULL。導(dǎo)致產(chǎn)生“野指針”。

            程序中的對象調(diào)用關(guān)系過于復(fù)雜,實在難以搞清楚某個對象究竟是否已經(jīng)釋放了內(nèi)存,此時應(yīng)該重新設(shè)計數(shù)據(jù)結(jié)構(gòu),從根本上解決對象管理的混亂局面。(這點可是深有感受,呵呵)

             不要忘記為數(shù)組和動態(tài)內(nèi)存賦初值。防止將未被初始化的內(nèi)存作為右值使用。

            windows如何下防止內(nèi)存泄露

            windows下開發(fā)C++程序的時候,我們經(jīng)常需要用到malloc開申請內(nèi)存,然后利用free回收內(nèi)存,但是開發(fā)人員的不小心可能會忘記free掉內(nèi)存,這樣就導(dǎo)致了內(nèi)存泄露

            利用庫檢測內(nèi)存泄露信息

            #define _CRTDBG_MAP_ALLOC  //如果沒有這個宏定義,我們只能知道有內(nèi)存泄露,卻無法知道在哪個地方申請內(nèi)存忘記了釋放

            #include
            <stdlib.h>
            #include
            <crtdbg.h>
            int main(void)
            {
                
            char *= (char *)malloc(sizeof(char* 100);
                _CrtDumpMemoryLeaks();
            }
            使用crtdbg來檢測到內(nèi)存泄露很簡單,只要在文件的第一行定義_CRTDBG_MAP_ALLOC,然后include頭文件crtdbg.h,在程序需要內(nèi)存檢測的地方調(diào)用_CrtDumpMemoryLeaks,就可以輸出內(nèi)存泄露的信息,如上面的程序,我們申請了100個字節(jié)的內(nèi)存而沒有釋放,但是我們可以很清楚地看到內(nèi)存泄露在 哪個地方。

             

            我們在main.cpp這個文件中的第八行申請了內(nèi)存,但是沒有進(jìn)行釋放

            那么編譯器是怎么知道我們有內(nèi)存泄露呢??就是利用宏定義把我們的調(diào)用的malloc替換成crtdbg 庫里面的_malloc_dbg,我們在申請內(nèi)存的時候,_malloc_dbg會先記錄下我們申請內(nèi)存的行數(shù)以及大小(記得編譯器有內(nèi)置的宏定義__LINE____FILE__?),把這些信息放到一個list(只是舉例,使用list保存這些信息一旦程序大了會很慢)里面,當(dāng)我們free內(nèi)存的時候,把這塊內(nèi)存的信息從list里面刪除掉,我們調(diào)用_CrtDumpMemoryLeaks()的時候就是在把這個list的信息依次打印出來而已

            下面是我們定義_CRTDBG_MAP_ALLOC后實際上所調(diào)用的malloc原型,malloc已經(jīng)成了一個宏定義

            #define   malloc(s)             _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)

            當(dāng)然,我們一般調(diào)用_CrtDumpMemoryLeaks的時候都是在程序的結(jié)尾處,如果我們的程序有多個出口,我們值需要在程序開始處調(diào)用_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ) 就可以了

            有時候我們需要檢測某一段代碼是否有內(nèi)存泄露,crtdbg庫也可以幫我們做到

            _CrtMemState s1;
            _CrtMemState s2;  
            _CrtMemCheckpoint(
            &s1); 
            char *p2 = (char *)malloc(400);
            _CrtMemCheckpoint(
            &s2);
            _CrtMemState s3;
            if (_CrtMemDifference(&s3,&s1,&s2))
            {
                 _CrtMemDumpStatistics(
            &s3);
            }

             

            這樣,我們在輸出窗口將會看到s1s2之間的內(nèi)存使用信息:

            0 bytes in 0 Free Blocks. 400 bytes in 1 Normal Blocks. 0 bytes in 0 CRT Blocks. 0 bytes in 0 Ignore Blocks. 0 bytes in 0 Client Blocks. Largest number used: 0 bytes. Total allocations: 400 bytes.

            crtdbg庫也有缺點,當(dāng)你使用一個別人提供的lib或者dll庫的時候,你調(diào)用這個函數(shù),這個函數(shù)分配了內(nèi)存,需要你去調(diào)用另外一個函數(shù)才能把內(nèi)存釋放掉,但是你不知道這個函數(shù)需要調(diào)用另外一個函數(shù)才能釋放掉內(nèi)存,這個是無法通過crtdbg庫檢測出來的,這個函數(shù)包括c++new函數(shù),所以這個庫實際上不適用C++

            利用share_ptr來管理內(nèi)存

            如果有使用過boost庫的應(yīng)該知道,boost里面有一個shart_ptr被譽(yù)為神器,因為他可以幫我們自動管理內(nèi)存,具體用法很簡單:

              boost::shared_ptr < connection > p ( new connection());

            這樣的話我們不需要去delete內(nèi)存,shartd_ptr會在我們不需要這快內(nèi)存的時候幫我們delete掉,shartd_ptr內(nèi)部是使用引用計數(shù)以及C++RAII,有別的對象引用該指針的時候引用技術(shù)就+1shartd_ptr析構(gòu)函數(shù)調(diào)用的時候引用計數(shù)就-1,當(dāng)為0的時候就delete掉該指針,所以我們并不需要調(diào)用delete來釋放資源,share_ptr會幫我們管理

            shared_ptr雖然看起來很好用,但是當(dāng)程序一旦復(fù)雜起來,shared_ptr依然也會變復(fù)雜(shared_ptr四宗罪),當(dāng)然boost本身就比較復(fù)雜,這個也是我比較不喜歡boost的一個原因

            將資源集中管理

            這個也是我比較經(jīng)常使用的方法,特別是在大程序的使用,配合單件模式,將資源在整個程序或者模塊中集中管理,這樣在程序結(jié)束的時候只要我們在析構(gòu)函數(shù)里面有清理這些資源,我們就可以避免內(nèi)存泄露,對于數(shù)據(jù)的一些寫操作全部在這個類中統(tǒng)一操作,如果要暴露內(nèi)部的數(shù)據(jù),只對外提供const數(shù)據(jù)(可以通過強(qiáng)轉(zhuǎn)去掉const屬性)

            當(dāng)然這個方法并不適用于所有場景,比如我們需要提供庫給別人,我們沒辦法預(yù)測到客戶需要什么操作,所以這個方法只適用內(nèi)部團(tuán)隊開發(fā)

            總之內(nèi)存管理據(jù)我所知到現(xiàn)在還是沒有什么好的解決方法,特別是當(dāng)代碼一旦膨脹的時候,到現(xiàn)在好像javapythonerlang都有內(nèi)存泄露的問題,我們只能在平常開發(fā)中多注意了

            參考資料:

            陳碩的博客(有一些shared_ptr的資料,也可以從這里看出shared_ptr使用起來沒那么簡單)
            shared_ptr四宗罪
            MSDN crtdbg庫

            本文相關(guān)鏈接:http://blog.csdn.net/na_he/article/details/7429171
                            http://www.cnblogs.com/linyilong3/archive/2013/03/23/2977247.html

            posted on 2013-05-02 18:29 王海光 閱讀(7167) 評論(0)  編輯 收藏 引用 所屬分類: C++
            88久久精品无码一区二区毛片| 久久综合久久鬼色| 久久水蜜桃亚洲av无码精品麻豆| 99久久国产亚洲综合精品| 久久久精品人妻一区二区三区蜜桃 | 久久精品国产一区二区三区日韩| 久久精品国内一区二区三区| 日韩久久久久中文字幕人妻| 久久精品国产精品亚洲精品| 国内精品久久久久伊人av| 久久亚洲高清综合| 久久99国产精一区二区三区| 亚洲AⅤ优女AV综合久久久| 婷婷伊人久久大香线蕉AV | 无码人妻久久一区二区三区| 国产毛片久久久久久国产毛片| 久久天天躁狠狠躁夜夜不卡| 一级做a爱片久久毛片| 天天躁日日躁狠狠久久| 久久中文字幕视频、最近更新| 久久亚洲春色中文字幕久久久| 亚洲AV伊人久久青青草原| 久久免费小视频| 成人国内精品久久久久影院| 18禁黄久久久AAA片| 欧美伊人久久大香线蕉综合69| 久久久国产精品福利免费| 久久精品亚洲中文字幕无码麻豆| 97视频久久久| 亚洲国产精品无码久久青草 | 国产免费久久精品99久久| 亚洲AV无码1区2区久久| 狠狠色丁香婷婷久久综合| 热久久国产欧美一区二区精品| 国产精品美女久久久久AV福利| 国产精品美女久久久m| 久久精品亚洲一区二区三区浴池| 久久久久久久久久久久久久| 久久久久亚洲AV片无码下载蜜桃| 午夜精品久久久久9999高清| 思思久久精品在热线热|