記憶體洩漏

記憶體洩漏是計算機科學中的一種資源洩漏，主因是計算機程式的記憶體管理失當，因而失去對一段已分配記憶體空間的控制，程式繼續佔用已不再使用的記憶體空間，或是記憶體所儲存之物件無法透過執行程式碼而存取，令記憶體資源空耗。記憶體漏失與許多其他問題情形具有相同徵兆，通常只有獲得程式原始碼的程式設計師能分析診斷是否發生記憶體漏失#重定向 。後果.記憶體洩漏會因為減少可用記憶體的數量從而降低計算機的效能。最終，在最糟糕的情況下，過多的可用記憶體被分配掉導致全部或部分裝置停止正常工作，或者應用程式崩潰。記憶體洩漏帶來的後果可能是不嚴重的，有時甚至能夠被常規的手段檢測出來。在現代作業系統中，一個應用程式使用的常規記憶體在程式終止時被釋放。這表示一個短暫執行的應用程式中的記憶體洩漏不會導致嚴重後果。在以下情況，記憶體洩漏後果較嚴重：簡例.以下的虛構例子無需任何程式設計的知識，但能表明如何導致記憶體洩漏及其造成的影響。在此例中的應用程式是一個簡單軟體的一小部分，用來控制升降機的運作。此部分軟體當乘客在升降機內按下一樓層的按鈕時執行。當按下按鈕時：#等待直至升降機停止#到達指定樓層#釋放剛才用作記住目的樓層的記憶體此程式有一處會造成記憶體洩漏：如果在升降機所在樓層按下該層的按鈕，程式將觸發判斷條件而結束執行，但記憶體仍一直被佔用而沒有被釋放。這種情況發生得越多，洩漏的記憶體也越多。這個小錯誤不會造成即時影響。因為人不會經常在升降機所在樓層按下同一層的按鈕。而且在通常情況下，升降機應有足夠的記憶體以應付上百次、上千次類似的情況。不過，升降機最後仍有可能消耗完所有記憶體。這可能需要數個月或是數年，所以在簡單的測試下這個問題不會被發現。而這個例子導致的後果會是不那麼令人愉快。至少，升降機不會再理會前往其他樓層的要求。更嚴重的是，如果程式需要記憶體去開啟升降機門，那可能有人被困升降機內，因為升降機沒有足夠的記憶體去開啟升降機門。記憶體洩漏只會在程式執行的時間內持續。例如：關閉升降機的電源時，程式終止執行。當電源再度開啟，程式會再次執行而記憶體會重置，而這種緩慢的洩漏則會從頭開始再次發生。程式設計問題.記憶體洩漏是程式設計中一項常見錯誤，特別是使用沒有內建自動垃圾回收的程式語言，如C及C++。一般情況下，記憶體洩漏發生是因為不能存取動態分配的記憶體。目前有相當數量的除錯工具用於檢測不能存取的記憶體，從而可以防止記憶體洩漏問題，如、、Valgrind、及都是為C／C++程式設計亦較受歡迎的記憶體除錯工具。垃圾回收則可以應用到任何程式語言，而C／C++也有此類函式庫。提供自動記憶體管理的程式語言如Java、C、C#、VB.NET以及LISP，都不能避免記憶體洩漏。例如，程式會把專案加入至列表，但在完成時沒有移除，如同人把物件丟到一堆物品中或放到抽屜內，但後來忘記取走這件物品一樣。記憶體管理器不能判斷專案是否將再被存取，除非程式作出一些指示表明不會再被存取。譬如以C語言為例，在stdlib.h中提供了 malloc()、calloc()、free()等函式，在使用malloc()取得記憶體空間，則需在不需使用後free()釋放，如未釋放，則會產生所謂memory leakage。雖然記憶體管理器可以回復不能存取的記憶體，但它不可以釋放可存取的記憶體因為仍有可能需要使用。現代的記憶體管理器因此為程式設計員提供技術來標示記憶體的可用性，以不同級別的「存取性」表示。記憶體管理器不會把需要存取可能較高的物件釋放。當物件直接和一個強引用相關或者間接和一組強引用相關表示該物件存取性較強。要防止此類記憶體洩漏，開發者必須使用物件後清理引用，一般都是在不再需要時將引用設成null，如果有可能，把維持強引用的事件偵聽器全部登出。一般來說，自動記憶體管理對開發者來講比較方便，因為他們不需要實現釋放的動作，或擔心清理記憶體的順序，而不用考慮物件是否依然被引用。對開發者來說，瞭解一個引用是否有必要保持比了解一個物件是否被引用要簡單得多。但是，自動記憶體管理不能消除所有的內容洩漏。影響.如果一個程式存在記憶體洩漏並且它的記憶體使用量穩定增長，通常不會有很快的症狀。每個物理系統都有一個較大的記憶體量，如果記憶體洩漏沒有被中止的話，它遲早會造成問題。大多數的現代計算機作業系統都有儲存在RAM晶片中主記憶體和儲存在次級儲存裝置如硬碟中的虛擬記憶體，記憶體分配是動態的——每個程序根據要求獲得相應的記憶體。存取活躍的頁面檔案被轉移到主記憶體以提高存取速度；反之，存取不活躍的頁面檔案被轉移到次級儲存裝置。當一個簡單的程序消耗大量的記憶體時，它通常佔用越來越多的主記憶體，使其他程式轉到次級儲存裝置，使系統的執行效率大大降低。甚至在有記憶體洩漏的程式終止後，其他程式需要相當長的時間才能切換到主記憶體，恢復原來的執行效率。當系統所有的記憶體全部耗完後。現在有一些專門為修復這種情況而設計的程式，常用的辦法是預留一些記憶體。值得注意的是，第一個遭遇得不到記憶體問題的程式有時候並不是有記憶體洩漏的程式。一些多工作業系統有特殊的機制來處理記憶體耗盡得情況，如隨機終止一個程序。另一些作業系統則有記憶體分配限制，這樣可以防止任何一個程序耗用完整個系統的記憶體。這種設計的缺點是有時候某些程序確實需要較大數量的記憶體時，如一些處理影象，影片和科學計算的程序，作業系統需要重新配置。如記憶體洩漏發生在核心，表示作業系統自身發生了問題。那些沒有完善的記憶體管理的計算機，如嵌入式系統，會因為一個長時間的記憶體洩漏而崩潰。一些被公眾訪問的系統，如網路伺服器或路由器很容易被駭客攻擊，加入一段攻擊程式碼，而產生記憶體洩漏。其他記憶體消耗.值得注意的是，記憶體用量持續增加不一定表明記憶體洩漏。一些應用程式會儲存越來越多資料到記憶體中（如用作快取。如果快取太大引起問題，這可能是程式設計上的錯誤，但並非是記憶體洩漏因為資料仍被使用。另一方面，程式有可能申請不合理的大量記憶體，因為程式設計者假設記憶體總是足夠執行特定的工作；例如，影象檔案處理器會在開始時閱讀影象檔案的內容並把之儲存至記憶體中，有時候由於影象檔案太大，消耗的記憶體超過了可用的記憶體導致失敗。另一角度講，記憶體洩漏是一種特殊的程式設計錯誤，如果沒有原始碼，根據徵兆只能猜測可能有記憶體洩漏。在這種情況下，使用術語“記憶體消耗持續增加”可能更確切。例子.C.下面是一個C語言的例子，在函式f()中申請了記憶體卻沒有釋放，導致記憶體洩漏。當程式不停地重複呼叫這個有問題的函式f，申請記憶體函式malloc()最後會在程式沒有更多可用記憶體可以申請時產生錯誤。但是，由於函式malloc()輸出的結果沒有加以出錯處理，因此程式會不停地嘗試申請記憶體，並且在系統有新的空閒記憶體時，被該程式佔用。注意，malloc()返回NULL的原因不一定是因為前述的沒有更多可用記憶體可以申請，也可能是邏輯地址空間耗盡，在Linux環境上測試的時候後者更容易發生。#include#includevoid f(void)void* s;s = malloc(50); /* 申請記憶體空間 */return; /* 內在洩漏 - 下資料 */* s 指向新分配的堆空間。* 當此函式返回，離開區域性變數s的作用域後將無法得知s的值，* 分配的記憶體空間不能被釋放。* 如要「修復」這個問題，必須想辦法釋放分配的堆空間，* 也可以用alloca(3)代替malloc(3)。* int main(void)/* 該函式是一個死迴圈函式 */while (true) f(); /* Malloc函式遲早會由於記憶體洩漏而返回NULL*/return 0;C++.以下例子中，儲存了整數123的記憶體空間不能被刪除，因為地址丟失了。這些空間已無法再使用。using namespace std;int main()int *a = new int(123);cout « *a « endl;// We should write "delete a;" herea = new int(456);cout « *a « endl;delete a;return 0;