有人能幫我理解內存泄漏的概念以及具體數據結構如何促進/阻止它(例如鏈表,數組等)。我前段時間被兩個不同的人教過兩次 - 由於教學方法的不同,這讓我略微困惑。內存泄漏...解釋(希望)
回答
維基百科有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);
}
}
}
基本上,當程序分配內存時,會發生內存泄漏,即使不再需要它也不會釋放內存。
- 在像C這樣的手動內存管理的語言中,這種情況發生在程序未能顯式釋放堆內存時,即程序員總是需要做某些事情來避免內存泄漏。
- 在Java等垃圾收集基礎語言中,當程序無意中保持對不再需要的對象的引用時,就會發生這種情況。很多時候,當這些對象被添加到「全局」集合然後「被遺忘」(特別是隱式添加發生時)時會發生這種情況。
正如您從第二點看到的那樣,集合通常傾向於成爲內存泄漏的焦點,因爲它們包含的內容並不明顯,當它們由長壽命對象內部維護時,它們更是如此。
原型內存泄漏是保存在靜態變量(即最長壽命)中的緩存(即隱式維護的集合)。
由Vijay提供的答案顯示你如何產生內存泄漏。但是找到一旦程序增長超過幾行代碼,泄漏可能是一項相當困難的任務。
如果你在Linux上,valgrind可以幫助你找到泄漏。
在Windows上,您可以使用CRT Debug Heap,它顯示泄漏的內容,但不顯示分配的位置。要顯示,其中分配了泄漏內存,您可以使用Memory Validator,這很容易使用:或者在Memory Validator的引擎下運行程序,或者附加到正在運行的進程。不需要更改來源。他們提供了一個功能齊全的30天試用版。
在定義內存泄漏方面,我無法真正添加其他人所說的內容,但我可以給您一些關於何時會發生內存泄漏的說明。
,想到的第一種情況是一個函數,它的配置:
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;
,並在發生錯誤時對其進行修改,並且不使用任何結構中的返回,但允許子代碼和父代碼路徑繼續到程序末尾。
也就是說,線程和分叉總是會使調試更加困難,因爲它們的性質。
我同意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中所述。
- 1. C++內存泄漏 - 解釋分析
- 2. Scala解釋器scala.tools.nsc.interpreter.IMain內存泄漏
- 3. 解釋objgraph數字:內存泄漏?
- 4. 內存泄漏 - 釋放calloc
- 5. FFMPEG解碼 - 內存泄漏
- 6. 解決PHP內存泄漏
- 7. 瞭解內存泄漏
- 8. Android內存泄漏了解
- 9. 哈希表valgrind內存泄漏
- 10. 泄漏工具沒有發現泄漏,但內存不釋放
- 11. 內存泄漏
- 12. 內存泄漏:
- 13. 內存泄漏
- 14. 內存泄漏
- 15. 內存泄漏
- 16. 內存泄漏
- 17. 內存泄漏
- 18. 內存泄漏
- 19. 內存泄漏
- 20. 內存泄漏
- 21. 內存泄漏:
- 22. 內存泄漏
- 23. 內存泄漏
- 24. 內存泄漏
- 25. 內存泄漏
- 26. 內存泄漏
- 27. 內存泄漏
- 28. 內存泄漏
- 29. CGImageRef釋放和autoreleasepool後內存泄漏
- 30. gtk_file_chooser_get_filename,結果未釋放,內存泄漏?
所以現在你要求第三種解釋,用另一種方法? – 2011-02-02 14:41:41