內存泄漏以及常見的解決方法

C++真是門噁心的語言,除非有智能指針類型,不然時不時就要擔心要釋放,而且類型一堆堆,時刻得提防,搞個指針還得先置NULL,不然心情不好報個野指針,囧了。。。
看完以後感覺很不錯,就轉載了~

檢查是否資源泄漏的辦法之一:
在任務管理器 進程 查看 選擇列 裏面選擇:內存使用、虛擬內存大小、句柄數、線程數、USER對象、GDI對象
讓你的程序(進程)不退出,循環執行主流程很多遍,越多越好,比如1000000次甚至無限循環,記錄以上各數值,再隔至少一小時,越長越好,比如一個月,再記錄以上各數值。如果以上兩組數值的差較大或隨時間流逝不斷增加,則鐵定有對應資源的資源泄漏!

原文:http://blog.csdn.net/na_he/article/details/7429171?reload
1. 什麼是內存泄漏(memory leak)?

 指由於疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。內存泄漏並非指內存在物理上的消失,而是應用程序分配某段內存後,由於設計錯誤,失去了對該段內存的控制,因而造成了內存的浪費。 

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 的語言來講,我們主要關注兩種類型的內存泄漏:

   堆內存泄漏(Heap leak)。對內存指的是程序運行中根據需要分配通過malloc,realloc new等從堆中分配的一塊內存,再是完成後必須通過調用對應的 free或者delete 刪掉。如果程序的設計的錯誤導致這部分內存沒有被釋放,那麼此後這塊內存將不會被使用,就會產生Heap Leak. 

  系統資源泄露(Resource Leak).主要指程序使用系統分配的資源比如 Bitmap,handle ,SOCKET等沒有使用相應的函數釋放掉,導致系統資源的浪費,嚴重可導致系統效能降低,系統運行不穩定。  

3. 如何解決內存泄露?

內存泄露的問題其困難在於1.編譯器不能發現這些問題。2.運行時才能捕獲到這些錯誤,這些錯誤沒有明顯的症狀,時隱時現。3.對於手機等終端開發用戶來說,尤爲困難。下面從三個方面來解決內存泄露:

第一,良好的編碼習慣,儘量在涉及內存的程序段,檢測出內存泄露。當程式穩定之後,在來檢測內存泄露時,無疑增加了排除的困難和複雜度。

使用了內存分配的函數,要記得要使用其想用的函數釋放掉,一旦使用完畢。

Heap memory:

malloc\realloc ------  free

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

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

要特別注意數組對象的內存泄漏

     MyPointEX *pointArray =new MyPointEX [100];

      其刪除形式爲:

     delete []pointArray 

Resource Leak :對於系統資源使用之前要仔細看起使用方法,防止錯誤使用或者忘記釋放掉系統資源。

我們看MSDN上一個創建字體的例子:
 RECT rect;

HBRUSH hBrush;
FONT hFont;
hdc = BeginPaint(hWnd, &ps);
 hFont = reateFont(48,0,0,0,FW_DONTCARE,FALSE,TRUE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Impact"));

SelectObject(hdc, hFont); 
SetRect(&rect, 100,100,700,200);

SetTextColor(hdc, RGB(255,0,0));

DrawText(hdc, TEXT("Drawing Text with Impact"), -1,&rect, DT_NOCLIP);    

DeleteObject(hFont);  
 EndPaint(hWnd, &ps);

 

如果使用完成時候忘記釋放字體,就造成了資源泄漏。 

   對於基於引用計數的系統對象尤其要注意,因爲只有其引用計數爲0時,該對象才能正確被刪除。而其使用過程中有其生成的新的系統資源,使用完畢後,如果沒有及時刪除,都會影響其引用計數。

 IDNS *m_pDns//define a DNS object.

   If(NULL == m_pDns)

   IEnv_CreateInstance (m_pEnv,AEECLSID_DNS,(void **) (&m_pDns))

  }

 If(m_pDns)
{

    Char szbuff[256];

    IDNS_AddQuestions(M_pDns,AEEDNSTYPE_A,ADDDNSCLASS_IN,szbuff);

    IDNS_Start(m_pDns,this);

    const AEEDNSResponse * pDnsResponse = NULL;

   IDNS_GetResponse(pMe->m_pDns, &pDnsResponse);

…………………………………………………………

…………………………………………………………..

………………………………………………………..

}

DNS_Release(pMe->m_pDns);//當程序運行到此時,其返回值不是0,是1,其含義是程序已經產生內存泄露了,系統已經有一個由DNS所產生的內核對象沒有釋放,而當這段代碼多次執行之後,內存泄露將不斷增加……..

m_pDns=NULL;

  }

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

例2: 

Struct ITypeface *pTypeface;

if (pTypeface)

{

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

} 

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

IHFont **pihf=NULL;

   ITypeface_NewFontFromFile(ITypeface,……,&pihf).

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

   ITypeface_NewFontFromClassID(IType,……,&pihf)

 

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

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

下面給出簡單的sample來說明。

memchecker.h

structMemIns

{

    void * pMem;

    int m_nSize;

    char m_szFileName[256];

    int m_nLine;

    MemIns * pNext;

};

classMemManager

{

public:

    MemManager();

    ~MemManager();

private:

    MemIns *m_pMemInsHead;

    int m_nTotal;

public:

    static MemManager* GetInstance();

    void Append(MemIns *pMemIns);

    void Remove(void *ptr);

    void Dump(); 

 

};

void *operatornew(size_tsize,constchar*szFile, int nLine);

void operatordelete(void*ptr,constchar*szFile, int nLine);

 void operatordelete(void*ptr);

void*operatornew[] (size_tsize,constchar*szFile,int nLine);

void operatordelete[](void*ptr,constchar*szFile, int nLine);

void operatordelete[](void *ptr);

 

memechecker.cpp

#include"Memchecher.h"

#include<stdio.h>

#include<malloc.h>

#include<string.h>

 

MemManager::MemManager()

{

    m_pMemInsHead=NULL;

    m_nTotal=NULL;

}

MemManager::~MemManager()

{

 

}

voidMemManager::Append(MemIns *pMemIns)

{

    pMemIns->pNext=m_pMemInsHead;

    m_pMemInsHead = pMemIns;

    m_nTotal+= m_pMemInsHead->m_nSize;

 

}

voidMemManager::Remove(void *ptr)

{

    MemIns * pCur = m_pMemInsHead;

    MemIns * pPrev = NULL;

    while(pCur)

    {

        if(pCur->pMem ==ptr)

        {

           if(pPrev)

            {

               pPrev->pNext =pCur->pNext;

            }

           else

            {

               m_pMemInsHead =pCur->pNext;

            }

           m_nTotal-=pCur->m_nSize;

           free(pCur);

           break;

        }

        pPrev = pCur;

        pCur = pCur->pNext;

    }

 

}

voidMemManager::Dump()

{

    MemIns * pp = m_pMemInsHead;

    while(pp)

    {

        printf( "File is %s\n", pp->m_szFileName );

        printf( "Size is %d\n", pp->m_nSize );

        printf( "Line is %d\n", pp->m_nLine );

        pp = pp->pNext;

    }

 

}

 

voidPutEntry(void *ptr,intsize,constchar*szFile, int nLine)

{

    MemIns * p = (MemIns *)(malloc(sizeof(MemIns)));

    if(p)

    {

        strcpy(p->m_szFileName,szFile);

        p->m_nLine = nLine;

        p->pMem = ptr;

        p->m_nSize = size;

        MemManager::GetInstance()->Append(p);

    }

}

voidRemoveEntry(void *ptr)

{

    MemManager::GetInstance()->Remove(ptr);

}

 

 

void *operatornew(size_tsize,constchar*szFile, int nLine)

{

    void * ptr = malloc(size);

    PutEntry(ptr,size,szFile,nLine);

    return ptr;

}

voidoperatordelete(void *ptr)

{

    RemoveEntry(ptr);

    free(ptr);

}

void operatordelete(void*ptr,constchar * file, intline)

{

    RemoveEntry(ptr);

    free(ptr);

}

 

void*operatornew[] (size_tsize,constchar* szFile,intnLine)

{

    void * ptr = malloc(size);

    PutEntry(ptr,size,szFile,nLine);

    return ptr;

}

 

void operatordelete[](void *ptr)

{

    RemoveEntry(ptr);

    free(ptr);

}

 

void operatordelete[](void*ptr,constchar*szFile,intnLine)

 {

    RemoveEntry(ptr);

    free(ptr);

}

#definenewnew(__FILE__,__LINE__)

MemManagerm_memTracer;

 

MemManager*MemManager::GetInstance()

{

    return &m_memTracer;

void main()

{

    int *plen =newint ;

    *plen=10;

    delete plen;

    char *pstr=newchar[35];

    strcpy(pstr,"hello memory leak");

    m_memTracer.Dump();

    return ;

}

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

4. 由內存泄露引出內存溢出話題:

所謂內存溢出就是你要求分配的內存超出了系統能給你的,系統不能滿足需求,於是會產生內存溢出的問題。

常見的溢出主要有:

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

內存分配雖然成功,但是尚未初始化就引用它。
內存分配成功並且已經初始化,但操作越過了內存的邊界。
例如在使用數組時經常發生下標“多1”或者“少1”的操作。特別是在for 循環語句中,循環次數很容易搞錯,導致數組操作越界。

使用free 或delete 釋放了內存後,沒有將指針設置爲NULL。導致產生“野指針”。

程序中的對象調用關係過於複雜,實在難以搞清楚某個對象究竟是否已經釋放了內存,此時應該重新設計數據結構,從根本上解決對象管理的混亂局面。(這點可是深有感受,呵呵)

不要忘記爲數組和動態內存賦初值。防止將未被初始化的內存作爲右值使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章