2011-02-02 136 views
4

有人能幫我理解內存泄漏的概念以及具體數據結構如何促進/阻止它(例如鏈表,數組等)。我前段時間被兩個不同的人教過兩次 - 由於教學方法的不同,這讓我略微困惑。內存泄漏...解釋(希望)

+1

所以現在你要求第三種解釋,用另一種方法? – 2011-02-02 14:41:41

回答

7

維基百科有good description on memory leaks。定有把定義爲:

A memory leak, in computer science (or leakage, in this context), occurs 
when a computer program consumes memory but is unable to release it back 
to the operating system. 

例如,下面的C函數泄漏內存:

void leaky(int n) 
{ 
    char* a = malloc(n); 
    char* b = malloc(n); 
    // Do something with a 
    // Do something with b 
    free(a); 
} 

上述功能泄漏n字節的內存作爲程序員忘記調用free(b)。這意味着操作系統的內存減少了n,以滿足進一步調用malloc。如果程序多次調用leaky,操作系統最終可能會耗盡內存,它可能會分配給其他任務。

至於你的問題的第二部分,沒有什麼內在的數據結構,使他們泄漏內存,但不小心的數據結構的實現可能會泄漏內存。作爲一個例子,考慮下面的函數從鏈表中刪除一個元素:

// I guess you could figure out where memory is leaking and fix it. 

void delete_element(ListNode* node, int key) 
{ 
    if (node != NULL) 
    { 
     if (node->key == key) 
     { 
      if (node->prev != NULL) { 
       // Unlink the node from the list. 
       node->prev->next = node->next; 
      } 
     } 
     else 
     { 
      delete_element(node->next, key); 
     } 
    } 
} 
1

基本上,當程序分配內存時,會發生內存泄漏,即使不再需要它也不會釋放內存。

  • 在像C這樣的手動內存管理的語言中,這種情況發生在程序未能顯式釋放堆內存時,即程序員總是需要做某些事情來避免內存泄漏。
  • 在Java等垃圾收集基礎語言中,當程序無意中保持對不再需要的對象的引用時,就會發生這種情況。很多時候,當這些對象被添加到「全局」集合然後「被遺忘」(特別是隱式添加發生時)時會發生這種情況。

正如您從第二點看到的那樣,集合通常傾向於成爲內存泄漏的焦點,因爲它們包含的內容並不明顯,當它們由長壽命對象內部維護時,它們更是如此。

原型內存泄漏是保存在靜態變量(即最長壽命)中的緩存(即隱式維護的集合)。

0

Vijay提供的答案顯示你如何產生內存泄漏。但是找到一旦程序增長超過幾行代碼,泄漏可能是一項相當困難的任務。

如果你在Linux上,valgrind可以幫助你找到泄漏。

在Windows上,您可以使用CRT Debug Heap,它顯示泄漏的內容,但不顯示分配的位置。要顯示,其中分配了泄漏內存,您可以使用Memory Validator,這很容易使用:或者在Memory Validator的引擎下運行程序,或者附加到正在運行的進程。不需要更改來源。他們提供了一個功能齊全的30天試用版。

0

在定義內存泄漏方面,我無法真正添加其他人所說的內容,但我可以給您一些關於何時會發生內存泄漏的說明。

,想到的第一種情況是一個函數,它的配置:

int* somefunction(size_t sz) 
{ 
    int* mem; 
    mem = malloc(sz*sizeof(int)); 
    return mem; 
} 

沒有什麼inheritently錯寫一個函數這種方式。這與malloc非常相似。問題是,你現在開始做:

int* x = somefunction(5); 

它容易忘記,現在不是一個malloc,以釋放x。再一次,沒有關於這個,這意味着你忘記,但我的經驗告訴我,這是我和其他人忽視的事情。

解決此問題的一個好方法是在函數命名中指示分配發生。所以,請撥打功能somefunction_alloc

想到的第二種情況是線程,尤其是fork(),因爲代碼全都在一個地方。如果你用函數,多個文件等整齊地編寫代碼,你幾乎總是會避免錯誤,但是要記住,一切都必須在某個範圍內釋放,包括在fork()和pre fork之間分配的東西。考慮這個:

int main() 
{ 
    char* buffer = malloc(100*sizeof(char)); 
    int fork_result = fork(); 

    if (fork_result < 0) 
    { 
     printf("Error\n"); 
     return 1; 
    } 
    elseif (fork_result == 0) 
    { 
     /* do child stuff */ 
     return 0; 
    } 
    else 
    { 
     /* do parent stuff */ 
    } 

    free(buffer); 
    return 0; 
} 

這裏有一個微妙的錯誤。父母不會泄漏任何內存,但子女確實是,因爲它是父級,包括堆的確切副本,但在釋放任何內容之前它會退出。免費必須在兩個代碼路徑上發生。同樣,如果分叉失敗,你仍然沒有釋放。當你編寫這樣的代碼時很容易錯過。更好的方法是創建一個退出代碼變量,如int status = 0;,並在發生錯誤時對其進行修改,並且不使用任何結構中的返回,但允許子代碼和父代碼路徑繼續到程序末尾。

也就是說,線程和分叉總是會使調試更加困難,因爲它們的性質。

2

我同意Vijay's大部分答案,但重要的是要注意,當引用堆塊(指針)時會發生泄漏。這兩種常見的原因是:

1 - 失去指針

void foo(void) 
{ 
    char *s; 
    s = strdup("U N I C O R N S ! ! !"); 
    return; 
} 

在上述範圍,我們已經失去了指針s的範圍,所以我們絕對沒有辦法釋放它。該內存現在在(地址)空間中丟失,直到程序退出並且虛擬內存子系統回收該進程所擁有的所有內容。

但是,如果我們只是將函數更改爲return strdup("U N I C O R N S ! ! !");,我們仍然會參考strdup()分配的塊。

2 - 重新分配指針沒有保存原始

void foo(void) 
{ 
     unsigned int i; 
     char *s; 

     for (i=0; i<100; i++) 
     s = strdup("U N I C O R N S ! ! !"); 

     free(s); 
    } 

在這個例子中,我們已經失去了99個引用塊是s曾經指出,所以我們實際上只釋放一個塊最後。同樣,這個內存現在丟失,直到程序退出後操作系統回收它。

另一個典型的誤解是,如果程序在退出之前沒有釋放內存,則在程序出口處仍然可以訪問的內存會泄漏。這在很長一段時間內並非如此。泄漏只發生在無法取消引用先前分配的塊以釋放它時。

還應該注意的是,處理static存儲類型有點不同,如this answer中所述。