內存泄漏與檢測

  動態分配的內存在程序結束後而一直未釋放,就出現了內存泄漏。網絡

通常常說的內存泄漏是指堆內存的泄漏。堆內存是指程序從堆中分配的,大小任意的(內存塊的大小能夠在程序運行期決定),使用完後必須顯式釋放的內存。多線程

應用程序通常使用malloc,new等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用free或delete釋放該內存塊,模塊化

不然,這塊內存就不能被再次使用,就說這塊內存泄漏了。函數

 

接着來分析下面的C代碼:this

void GetMemory(char *p)
{
    p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}
請問運行Test 函數會有什麼樣的結果?
分析:上面的代碼試圖使用指針做爲參數,分配動態內存。該代碼會存在兩個問題:
1. 內存泄漏。
首先,經過指針做爲參數沒法成功申請一塊動態分配的內存。這是由於,GetMemory()函數得到的是實參指針變量的一個拷貝。所以,它只是將新分配的內存賦給了形參(即實參指針的拷貝)。而實參並無得到這塊內存。在Test()函數中,發現並無釋放str指向內存的語句。但這不是內存泄露的根本緣由。即便在程序後面加上一句: 
free(str);
內存依然會泄漏。這是由於,str根本沒有得到這塊內存,而是由形參得到了。而形參是一個棧上的變量。在函數執行以後就已經被系統收回了。也就是說,有一塊內存在分配以後,被遺忘了,這是形成了內存泄漏的根本緣由。 要想成功得到分配的內存,能夠採用下面的兩種方法:
char* GetMemory(char *p)
{
    p = (char *)malloc(100);
    return p;
}
上面的代碼直接返回新分配的內存。因爲內存是在堆上而不是在棧上分配的,因此函數返回後不存在任何問題。
或者:
void GetMemory(char **p, int num)
{
    *p = (char *)malloc(num);
}
這種方法是經過指針的指針來分配內存。用這種方法分配內存,傳遞給函數的是指針地址的一個拷貝,那麼*p就是指針自己。 所以新分配的內存成功的賦給了作實參的指針。
2. NULL指針引用致使程序崩潰。
因爲str並無得到這塊內存,那麼str的值依然爲NULL,因此strcpy()函數訪問了一個NULL指針,直接致使程序崩潰。
void GetMemory(char **p, int num)
{
    *p = (char *)malloc(num);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf(str);
}
請問運行Test 函數會有什麼樣的結果?
分析:上面的代碼使用指針的指針來分配內存,str會成功得到分配的內存。 可是,該題在使用了指針後,卻忘記了對內存的釋放。因此應該在後面加上:
free(str);
str = NULL;
內存泄漏自己影響並不大,一次普通的內存泄漏用戶根本感受不到內存泄漏的存在。真正影響大的是內存泄漏的堆積,這會最終消耗盡系統全部的內存。所以,在平時編碼之時應該提升警戒,在使用完動態分配的內存以後,及時釋放掉。 
那麼如何防止內存泄漏呢?內存分配應該遵循下面的原則:
1. 誰分配,誰釋放。在寫下new/malloc時,要立刻寫下配對的delete/free以此釋放掉。
2. 出錯處理需釋放。在函數錯誤處理分支中,記得釋放掉已經分配的內存。
爲了應對這種複雜的出錯處理邏輯,避免一不當心就忘記了釋放分配的資源,能夠採用出錯處理模塊化處理, 在函數的尾部增長錯誤處理模塊。一旦出錯,就利用goto語句跳轉到出錯處理模塊集中處理出錯狀況下資源的釋放。 此外,還能夠用SHE __try__leave__except結果化異常處理機制來處理系統中的異常的發生時資源的釋放。
3. 網絡上拷貝的代碼,要仔細檢查內存使用狀況,預防內存泄漏。
4. 對於複雜指針的使用,若是作不到「誰分配,誰釋放」,那麼可使用引用計數來管理這塊內存的使用。 引用計數方式來管理內存,即在類中增長一個引用計數,跟蹤指針的使用狀況。當計數爲0了,就能夠釋放指針了。 此種方法適合於經過一個指針申請內存以後,會通過程序各類複雜引用的狀況。
下面是一個實際例子:
class CXData

public:
CXData()
{
    m_dwRefNum = 1; //引用計數賦初值
}
ULONG AddRef() //增長引用
{
    ULONG num = InterlockedIncrement(&m_dwRefNum);
    return num;
}
ULONG Release() //減小引用
{
    ULONG num = InterlockedDecrement(&m_dwRefNum);
    if(num == 0) //當計數爲0了,就釋放內存
    {
        delete this;
    }
    return num;
}
private: ULONG m_dwRefNum; //引用計數
}
使用實例:
CXData *pXdata = new CXData;
pXdata->AddRef(); //使用前增長計數
pXdata->Release(); //使用後減小計數,若是計數爲零,則釋放內存

此外,內存泄漏還可能由調用了不正確的系統API而形成。好比在Windows裏的CreateThread函數。編碼

CreateThread:是Windows的API函數(SDK函數的標準形式,直截了當的建立方式,任何場合均可以使用),提供操做系統級別的建立線程的操做,且僅限於工做者線程。操作系統

 不調用MFC和RTL的函數時,能夠用CreateThread,其它狀況不要使用。由於: 線程

C Runtime中須要對多線程進行紀錄和初始化,以保證C函數庫工做正常。 MFC也須要知道新線程的建立,也須要作一些初始化工做。 設計

有些CRT的函數象malloc(),fopen(),_open(),strtok(),ctime(),或localtime()等函數須要專門的線程局部存儲的數據塊,這個數據塊一般須要在建立線程的時候就創建,若是使用CreateThread,這個數據塊就沒有創建,但函數會本身創建一個,而後將其與線程聯繫在一塊兒,這意味着若是你用CreateThread來建立線程,而後使用這樣的函數,會有一塊內存在不知不覺中建立,並且這些函數並不將其刪除,而CreateThread和ExitThread也沒法知道這件事,因而就會有Memory Leak,在線程頻繁啓動的軟件中,早晚會讓系統的內存資源耗盡。 

指針

思考題:

如何設計一個跨平臺的內存泄漏檢測機制?也就是說,若是在不一樣的平臺發生了內存泄漏,咱們如何用統一的方法檢測它?

相關文章
相關標籤/搜索