簡體   English   中英

C、C++ 中的內存泄漏; 忘了做免費,刪除

[英]Memory leak in C,C++; forgot to do free,delete

我們在 C 中使用 malloc 分配內存,在 C++ 中使用 new 分配內存。 我知道必須使用 C 中的 free 和 C++ 中的 delete 釋放分配的內存或將其返回給操作系統。 如果我在分配內存后忘記使用 free/delete,則意味着會有內存泄漏。

現在,我的問題是,這種內存泄漏是否僅在程序執行期間? 或者它是永久性泄漏/丟失還是在我重新啟動系統后再次獲得? 內部流程究竟是怎樣的? 內存泄漏/丟失究竟是什么意思?

如果有人能詳細解釋這一點或為我提供一些不錯的參考資料,我將不勝感激。

更新 1

看了一些答案,我了解到程序終止后內存會歸還給操作系統/系統,如果是這樣,為什么每個人都需要如此關心內存泄漏,為什么防止內存泄漏很重要?

更新 2

因此,應該防止內存泄漏,以便系統不會因分配內存不足而崩潰??

更新 3

因此,在閱讀所有答案后,我意識到內存泄漏是防止系統崩潰的重要問題。 但是,對於像我這樣的初學者,我如何確定我的程序是否完全沒有內存泄漏。 我嘗試免費,如果我使用 malloc,則刪除,新但有時,它會變得混亂。 有什么工具或方法可以讓我知道我的程序是否有內存泄漏?

更新 4

閱讀答案后,我現在了解了無內存泄漏代碼的重要性,更少使用 new/delete,更多使用 STL,學習了 RAII、valgrind 等新東西和良好的編程實踐。 謝謝大家 :)

這是每個進程 一旦您的進程退出,分配的內存將返回給操作系統供其他進程(新的或現有的)使用。

要回答您編輯的問題,您的機器中只有有限的內存量。 因此,如果您有內存泄漏,那么主要問題是該內存不可用於其他進程。 次要但不可忽略的影響是您的過程映像增長,您將切換到磁盤並且性能將受到影響。 最后,您的程序將耗盡系統中的所有內存並失敗,因為它無法為自己分配任何內存。

可以說,對於一個生命周期短的小進程來說,內存泄漏是可以容忍的,因為泄漏的內存數量少,生命周期短。

看看這個資源,可能比你需要的更多信息。 我們在這里討論的是動態分配或分配。

內存泄漏只是意味着您的應用程序無法釋放它已分配的內存。 程序結束后,由操作系統決定會發生什么。 每個現代操作系統都會回收應用程序使用的所有內存,因此一旦您的進程終止,它將被清理。

但是 C/C++ 不保證操作系統會這樣做。 在某些平台上,內存可能會一直丟失,直到您重新啟動。

所以內存泄漏的問題是雙重的:

  • 系統可能必須重新啟動才能回收內存的少數平台之一。 在大多數平台上,這不是問題,盡管泄漏一些其他資源類型可能仍然會導致問題。
  • 只要您的程序在運行,它就會分配永遠不會釋放的內存,這意味着它將使用越來越多的內存。 如果您的程序打算長時間運行,它最終可能會使用機器上的所有可用內存,並隨后崩潰。

許多短時間運行的程序實際上會忽略內存泄漏,因為它們知道操作系統很快就會清除它。 據我所知,微軟的 C++ 編譯器就是這樣做的。 他們知道,一旦調用編譯器,它最多會運行幾分鍾。 (他們知道它運行在Windows,其中OS確實回收存儲過程一旦終止的),所以沒關系,它在這里和泄漏一些內存出現。

至於如何避免內存泄漏,就不要創建了。

每次使用 new/delete 時都有泄漏內存的風險,所以不要.

當您需要一組數據時,請執行以下操作:

std::vector<char> vec(200);

而不是這個:

char* arr = new char[200];

前者同樣有效,但您不必顯式調用 delete 來釋放它std::vector使用 RAII 在內部管理其資源。 你也應該這樣做——要么使用現成的 RAII 類,如vectorshared_ptr ,或者標准庫或 Boost 中的幾乎任何其他類,或者通過編寫自己的類。

作為一般經驗法則,您的代碼不應包含任何 new/delete 調用,除了負責管理該分配的類的構造函數/析構函數。

如果一個對象在構造函數中分配了它需要的內存,並在析構函數中釋放它(並正確處理復制/賦值),那么你可以在需要的時候簡單地在堆棧上創建一個類的本地實例,它不會,不能,內存泄漏。

在 C++ 中不泄漏內存的關鍵是不調用 new/delete。

操作系統將跟蹤內存,一旦您的程序終止將回收所有內存。 這只是意味着您的應用程序丟失了某些已分配內存的跟蹤。

請注意,這可能不適用於某些操作系統,但適用於任何 windows/unix/mac 類型系統

Re: 檢測內存泄漏的工具

如果您使用基於 Linux 的操作系統進行開發,您可以嘗試使用 valgrind ( http://valgrind.org/ ) 來檢測內存泄漏。

valgrind --leak-check=full ./compiled_binary

如果您的程序是用調試符號編譯的(例如,對於 gcc,包括 -g 標志),valgrind 還會通知您分配泄漏內存的確切代碼行。 這將大大簡化跟蹤和修復泄漏的任務。

優點:它是免費的

缺點:AFAIK,它只適用於 Linux

更新

正如在http://valgrind.org/info/platforms.html 上看到的,valgrind 正在被移植到其他操作系統(和平台),包括 MacOSX、FreeBSD 和 NetBSD。

更新 2

(有點跑題,但是……)

使用 valgrind 的好處是它不僅僅檢查內存泄漏。 http://valgrind.org/info/tools.html

我將 buildbot 配置為對我所有的夜間構建運行 valgrind(和夾板),這已被證明是無價的!

有一些工具可以檢測內存泄漏,例如Purify

作為 C++ 程序員的新手,我能給你的最好建議是學習如何最大限度地減少你編寫的“new”和“delete”語句的數量。 如果您讓編譯器在堆棧上本地創建對象,它將為您管理內存,並在對象超出范圍時自動刪除它們。

有一種編程思想叫做資源獲取即初始化( RAII )。 這意味着“如果您需要分配需要刪除的內存,或者確保您打開的東西被關閉,請將其包裝在您在堆棧上創建的對象中。這樣當對象超出析構函數的范圍時自動被調用,然后您在析構函數中刪除您的資源。”

當您在代碼中編寫“new”時會發生常見的內存泄漏,但您的函數在調用 delete 之前退出。 有時您會過早地遇到“return”語句,有時會在“delete”語句之后拋出並捕獲異常。 遵循 RAII 可幫助您確保不會發生這些事故。

這是內存泄漏。

基本上這意味着在進程被銷毀之前不會回收這些內存。

問題是當指針超出范圍並且您不釋放內存時,它由進程分配,但程序無法知道它超出范圍並且不再需要(不使用類似的工具)瓦爾格林)。

如果它重復發生,這只是一個主要問題。 如果是這樣,那么程序在最終崩潰之前運行的時間越長,它就會繼續使用越來越多的內存。 用戶需要定期重新啟動應用程序以避免這種情況發生或使用過多的系統資源。

更新 1:
如果您有一個簡單的應用程序,它運行一次,執行它的操作並終止,那么內存泄漏就不是那么重要了。 這仍然是一種非常糟糕的做法,如果您的編碼風格完全允許代碼泄漏,那么您可能會將相同的泄漏放入重要的應用程序中 - 那些可以工作數天、數周或數年的應用程序。 我們這里有一個應用程序會泄漏,所以我們每個月都會重新啟動它。 它不是一個理想的情況。

更新 2:
是的,差不多。 但是應該防止內存泄漏僅僅因為它們是一個錯誤,並且您永遠不應該以錯誤是可以接受的觀點編寫代碼。

更新 3:
防止內存泄漏的最佳方法是首先不要使用 malloc/free。 如果您在 RAII 上使用 C++ 閱讀,使用類並復制對象,這將確保您永遠不會有泄漏......幾乎所有時間。 如果確實必須顯式分配內存,請確保對其進行跟蹤。 如果這意味着您需要一個全局對象來存儲指針,那么就這樣做。 如果這意味着您有一個存儲指針的集合類,則獲取一個。 永遠不要將內存分配給您可能會忘記的局部變量,可能會從函數返回而不通過 free 調用,可能會傳遞給另一個沒有被調用的函數以釋放。 為此需要一種紀律感(不需要太多紀律),但很多人會告訴你,無論如何編寫好的、正確的、設計良好的、無錯誤的代碼也需要同樣的美德(他們是對的 -如果您曾經見過與精心設計的代碼相比的黑客代碼,您將能夠立即看到差異)。

筆記:
即使使用垃圾收集語言,您仍然會遇到內存泄漏。 人們將對象添加到集合中,然后忘記刪除它們,因此對象永遠保留在內存中。 這算作泄漏。 在 GC 語言中這樣做是很常見的,因為人們認為 GC 會為他們做所有的工作。 同樣,編碼/設計紀律是必需的——知道你在做什么可以防止這些錯誤。

即使不使用 malloc/new,也可能發生內存泄漏。 注意覆蓋已經指向某些內存的指針變量。 我有史以來最大的泄漏源是使用 MSXML,我創建了一個 XML 對象的 CComPtr,然后調用該方法來獲取一個元素,將該對象作為參數傳遞給該方法。 不幸的是,該類被強制轉換為內部指針,該方法只會用新指針覆蓋它,從而導致舊數據泄露。 這里的寓意是,如果使用智能指針類,請確保您知道它在做什么,尤其是它的強制轉換運算符。

工具:
如果在 Windows 上運行,則無需購買 Purify。 Microsoft 提供了UMDH ,它可以對您的內存進行快照。 拍攝 2 個快照並使用該工具進行比較,您可以看到分配隨着時間的推移穩步增加而不會被取消分配。 它不漂亮,但它有效。

補充幾點:

  1. 從一開始就學會正確工作——釋放內存,改掉壞習慣很難。
  2. 內存不是應該使用 new/delete 管理或管理的唯一資源。

    例如,某個對象可能保存了一些應該在最后刪除的臨時文件。 通常這樣的東西被綁定到某個對象,所以,如果你忘記刪除對象,你忘記取消鏈接文件......即使系統重新啟動,該資源也不會回來。

    還有許多其他類似的資源綁定到對象並使用 new/delete 進行管理:文件、套接字、共享內存、數據庫連接等。 因此,您學習管理內存的所有技術都將幫助您管理您必須使用的其他非常有限的資源。

內存不會丟失,但它會保持分配狀態,因此不可用於您的程序進行的下一次分配。 這意味着如果你的程序繼續分配內存而不釋放它,它會消耗越來越多的內存。 一段時間后,剩下未分配的內存,下一次分配新內存的嘗試將失敗,因此您的程序也會失敗。

該內存取自所謂的“堆”。 這是您的程序本地的,並在您的程序完成時被完全刪除。 因此,您的程序對系統和操作系統中運行的其他程序可能造成的“唯一”傷害是它們也可能無法分配內存,因為您的程序已經“吃光”了所有程序。 一旦你終止你的程序,其他人應該正常運行,如果他們沒有因為分配問題在此期間崩潰。

監控內存泄漏的一個工具是覆蓋 new 和 delete 操作符。 這允許您維護已分配和未釋放的內存列表。 因此,如果某個特定對象應該釋放它正在使用的所有內存,則此機制為您提供了一種驗證它是否真的釋放了內存的方法。

除了 Purify,您還可以嘗試一個免費的替代方案:valgrind。 那里的規定是 valgrind 是特定於 linux 的解決方案。

要回答更新 3,通常有一種方法可以指示您的進程是否有任何未完成的內存分配 _heapwalk(在 Win32 下)將允許您逐步完成所有分配,您可以查看是否有任何未完成的分配。

除此之外,它可能值得包裝您的 malloc/new 調用,以便您在它發生時記錄每個分配的文件和行號。 然后在覆蓋的刪除/釋放中將其從列表中刪除。

例如(請注意,這是完全未經測試的代碼,因此可能無法立即使用)

struct MemoryAllocEntry
{
    char* pFile;
    char* pLine;
};

extern std::map< MemoryAllocEntry > g_AllocList;

inline void* MyMemAlloc( size_t size, char* pFile, char* pLine )
{
    MemoryAllocEntry mae;
    void* pRet = malloc( size );
    mae.pFile = pFile;
    mae.pLine = pLine;

    g_AllocList[pRet] = mae;

    return pRet;
}

inline void MyMemFree( void* pPtr )
{
    std::map< MemoryAllocEntry >::iterator iter = g_AllocList.find( pPtr );
    if ( iter != g_AllocList.end() )
    {
         g_AllocList.erase( iter );
    }
    free( pPtr );
}

#ifdef _DEBUG
    #define malloc( x ) MyMemAlloc( (x), __FILE__, __LINE__ )
    #define free( x ) MyMemFree( (x) )
#endif

然后您需要做的就是單步執行 g_AllocList 以查找任何未完成的分配。 以上顯然僅適用於 malloc 和 free 但您也可以使其適用於 new 和 delete (例如,MFC 可以做到)。

回答您的問題,並更新1:

並非所有操作系統都支持不同進程的概念,因此永遠不會自動清理。

例如,像 VxWorks 這樣的嵌入式操作系統(在某些配置中)不支持多進程的概念,因此即使在您的任務結束后,您未能釋放的任何內存仍將保留。 如果這不是故意的,您最終會出現內存泄漏。

此外,這樣的平台可能用在很少重新啟動且不支持交換的系統上,因此任何內存泄漏都比(例如)台式機上的內存泄漏嚴重得多。

避免內存泄漏的正確方法是不那么顯式地進行內存管理,並依賴於為您管理內容的容器,例如 C++ 中的 STL(它的相關部分)。

使用 C 的低級嵌入式程序員通常通過在啟動時靜態分配所有內容來避免內存泄漏。 C 程序員還可以使用 alloca() 的堆棧分配來避免可能的內存泄漏(但他們需要准確了解它是如何工作的)。

這是分配給進程的內存。 當你殺死進程時,你會得到它。

內存泄漏通常會導致長時間運行的程序出現問題; 在不幸的地方(例如循環)中僅僅幾個字節的泄漏會迅速擴大應用程序的內存占用。

回答編輯 -

如果您的程序要運行,. 所以有些東西然后終止然后不你不需要太擔心釋放內存。 這對於運行一段時間的程序很重要。 如果您的網絡瀏覽器沒有釋放用於顯示頁面的內存,它很快就會使用您計算機中的所有內存。

無論如何,釋放內存是一個好習慣,運行一次的小程序有一個習慣,它會變成其他東西,這是一個好習慣。

當程序在 Windows 中完成時,它不僅釋放內存,而且釋放所有句柄(如果我錯了,請糾正我)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM