簡體   English   中英

為什么我需要刪除[]?

[英]Why do I need to delete[]?

讓我們說我有這樣的功能:

int main()
{
    char* str = new char[10];

    for(int i=0;i<5;i++)
    {
        //Do stuff with str
    }

    delete[] str;
    return 0;
}
  1. 如果我要結束程序,為什么還需要刪除str 如果我要退出,我不在乎那個記憶是否會到達滿是獨角獸的土地,對吧?

  2. 這只是好習慣嗎?

  3. 它有更深的后果嗎?

如果事實上你的問題確實是“我有這個瑣碎的程序,那么在它退出之前我不會釋放幾個字節嗎?” 答案是肯定的,那沒關系。 在任何現代操作系統上都會很好。 該計划是微不足道的; 這並不是說你要把它放入心臟起搏器或用這個東西運行豐田凱美瑞的制動系統。 如果唯一的客戶是你,那么你唯一可能因為草率而可能影響的人就是你。

當你從這個問題的答案開始概括到非平凡的案例時,問題就出現了。

因此,讓我們提出兩個關於一些非平凡案例的問題。

我有一個長期運行的服務,以復雜的方式分配和釋放內存,可能涉及多個分配器打多個堆。 在正常模式下關閉我的服務是一個復雜且耗時的過程,涉及確保外部狀態 - 文件,數據庫等 - 始終關閉。 在關閉之前,我應該確保分配的每個內存字節都被釋放嗎?

是的,我會告訴你原因。 長期運行服務可能發生的最糟糕的事情之一就是它會意外泄漏內存。 即使很小的泄漏也會隨着時間的推移而增加大量泄漏。 查找和修復內存泄漏的標准技術是檢測分配堆,以便在關閉時記錄所有已分配但未釋放的資源。 除非你喜歡追逐大量誤報並在調試器中花費大量時間,否則即使這樣做並非嚴格必要,也要釋放你的記憶

用戶已經預計關閉服務可能需要數十億納秒,所以誰在乎你是否會對虛擬分配器造成一點額外的壓力,確保一切都被清理干凈? 這只是您為大型復雜軟件支付的價格。 並不是說你一直在關閉服務,所以再次,誰在乎它是否比它可能慢幾毫秒?

我有同樣長期運行的服務。 如果我發現我的一個內部數據結構已損壞,我希望“快速失敗”。 該程序處於未定義狀態,它可能以提升的權限運行,我將假設如果我檢測到已損壞的狀態,那是因為我的服務正在被敵對方主動攻擊。 最安全的做法是立即關閉服務。 我寧願允許攻擊者拒絕為客戶提供服務,也不願讓服務熬夜並進一步損害我的用戶數據。 在這個緊急關閉場景中,我應該確保我分配的每個內存字節都被釋放了嗎?

當然不是。 操作系統將為您處理。 如果您的堆已損壞,攻擊者可能希望您將內存作為其漏洞利用的一部分。 每毫秒都很重要。 為什么在你在建築物上放置戰術核武器之前,你還要打擾門把手和拖地廚房嗎?

所以問題的答案“我應該在程序退出之前釋放內存嗎?” 是“這取決於你的程序做什么”。

是的,這是好習慣。 你永遠不應該假設你的操作系統會照顧你的內存釋放,如果你養成這種習慣,它會在以后搞砸你。

但是,要回答您的問題,在退出main時,操作系統會釋放該進程占用的所有內存,因此包括您可能生成的任何線程或分配的變量。 操作系統將負責釋放內存以供其他人使用。

重要提示: delete內存幾乎只是一種副作用。 它的重要作用是破壞對象。 使用RAII設計,這可能意味着關閉文件,釋放操作系統句柄,終止線程或刪除臨時文件。

當您的進程退出時,操作系統會自動處理其中一些操作,但不是全部。

在您的示例中,沒有理由不調用delete 但也沒有理由要求new ,所以你可以這樣回避問題。

char str[10];

或者,您可以通過使用智能指針來回避刪除(以及涉及的異常安全問題)...

因此,通常您應該始終確保對象的生命周期得到妥善管理。

但這並不總是那么容易: 靜態初始化順序慘敗的變通方法通常意味着你別無選擇,只能依靠操作系統為你清理一些單例類型的對象。

相反的答案:不,這是浪費時間。 具有大量分配數據的程序幾乎必須觸及每個頁面才能將所有分配返回到空閑列表。 這會浪費CPU時間,為不感興趣的數據創建內存壓力,甚至可能導致進程從磁盤交換頁面。 只需退出即可將所有內存釋放回操作系統,無需任何進一步操作。

(不是我不同意“是”中的原因,我只是認為兩種方式都存在爭議)

退出程序時,您的操作系統應該處理內存並進行清理,但通常的做法是釋放您保留的內存。 我個人認為最好是進入這樣做的正確心態,因為當你做簡單的程序時,你很可能這樣做是為了學習。

無論哪種方式,保證釋放內存的唯一方法就是自己動手。

newdelete保留關鍵字兄弟。 他們應該通過代碼塊或通過父對象的生命周期相互合作。 每當弟弟犯錯( new )時,哥哥就會想要清理( delete )它。 然后母親(你的節目)將為他們感到高興和自豪。

我完全贊同Eric Lippert的出色建議:

所以問題的答案“我應該在程序退出之前釋放內存嗎?” 是“這取決於你的程序做什么”。

這里的其他答案提供了支持和反對兩者的論據,但問題的真正關鍵在於您的計划所做的事情。 考慮一個更重要的例子,其中動態分配的類型實例是自定義類,類析構函數執行一些產生副作用的動作。 在這種情況下,內存泄漏與否的爭論是微不足道的,更重要的問題是未能在這樣的類實例上調用delete將導致未定義的行為。

[basic.life] 3.8對象生命周期
第4段:

程序可以通過重用對象占用的存儲來結束任何對象的生命周期,或者通過使用非平凡的析構函數顯式調用類類型的對象的析構函數來結束任何對象的生命周期。 對於具有非平凡析構函數的類類型的對象,程序不需要在重用或釋放對象占用的存儲之前顯式調用析構函數; 但是, 如果沒有對析構函數的顯式調用或者如果沒有使用delete-expression(5.3.5)來釋放存儲,則不應該隱式調用析構函數,並且任何依賴於析構函數生成的副作用的程序有未完成的行為。

所以問題的答案就像埃里克所說“取決於你的程序做什么”

這是一個公平的問題,在回答時需要考慮以下幾點:

  • 某些對象具有更復雜的析構函數,這些析構函數在刪除時不會釋放內存。 他們可能有其他副作用,你不想跳過。
  • C ++標准無法保證在進程終止時釋放內存。 (當然在現代操作系統上它會被釋放,但如果你在一些奇怪的操作系統上沒有這樣做,你必須正確地釋放你的記憶
  • 另一方面,在程序退出時運行析構函數實際上可能占用相當多的時間,如果所有的操作都是釋放內存(無論如何都會釋放),那么是的,短路是很有意義的然后立即退出。

大多數操作系統將在進程退出時回收內存。 例外情況可能包括某些RTOS,舊移動設備等。

絕對意義上,您的應用程序不會泄漏內存; 但是,即使您知道它不會導致真正的泄漏,清理您分配的內存也是一種很好的做法。 這個問題是泄漏很多,更難以解決而不是讓它們開始。 假設您決定要將main()中的功能移動到另一個功能。 你最終可能會遇到真正的泄漏。

這也是糟糕的美學,許多開發人員會看到不一致的'str'並感到輕微的惡心:(

你有很多專業經驗的答案。 在這里,我說的是一個天真的,但我認為是一個回答。

  • 摘要

    3.它有更深的后果嗎?

    答:會詳細回答。

    2.難道只是好的做法呢?

    答:這被認為是一種很好的做法。 當您確定不再使用它時,釋放您檢索到的資源/內存。

    1. 如果我要結束程序,為什么還需要刪除str
      如果我要退出,我不在乎那個記憶是否會到達滿是獨角獸的土地,對吧?

    答:你需要或者不需要,事實上, 告訴為什么。 下面有一些解釋。

    我認為這取決於。 這是一些假設的問題; 術語“ 程序”可以指應用程序或功能。

    問:這取決於該計划的作用嗎?

    答:如果宇宙被摧毀是可以接受的,那么沒有。 但是,程序可能無法按預期正常工作,甚至可能是一個無法完成預期的程序。 您可能想認真考慮為什么要構建這樣的程序?

    問:這取決於程序的復雜程度嗎?

    答:不可以。見解釋。

    問:這取決於預期程序的穩定性嗎?

    答:關系密切。

    我認為這取決於

    1. 什么是程序的宇宙
    2. 該計划的期望是如何完成其​​工作的?
    3. 該計划對其他人及其所處的世界有多少關注?

      關於術語Univers ,請參閱說明。

    總結一下,這取決於在乎什么。


  • 說明

    重要提示:如果我們將術語“ 程序”定義為函數,那么它的Universe就是應用程序 省略了許多細節; 作為理解的一個想法,它已經足夠長了。

    我們可能已經看到過這種圖表,它說明了應用軟件和系統軟件之間的關系。

    9RJKM.gif

    但是為了了解其涵蓋范圍,我建議采用相反的布局。 由於我們僅討論軟件,因此下圖中省略了硬件層。

    mjLai.jpg

    通過這個圖,我們意識到操作系統涵蓋了最大的范圍,這是當前的宇宙 ,有時我們說環境 您可能會想象整個架構包含很多像圖表一樣的磁盤,無論是圓柱形還是圓環形(球很好但很難想象)。 在這里我要提到的是,最外層的OS實際上是一個整體 ,運行時可以是單個或多個不同的實現。

    運行時對OS和應用程序負責是很重要的,但后者更為關鍵。 運行時是應用程序的世界,如果它被銷毀,則在其下運行的所有應用程序都將消失。

    與地球上的人不同,我們住在這里,但我們不是由地球組成的; 如果地球正在摧毀而我們不在那里,我們仍將生活在其他合適的環境中。

    然而,當宇宙被摧毀時,我們就不復存在了,因為我們不僅生活在宇宙中,而且還包含宇宙。

    如上所述,運行時也對OS負責。 下圖中的左側圓圈可能是它的樣子。

    ScsZs.jpg

    這大部分就像操作系統中的C程序。 當應用程序和OS之間的關系與此匹配時,與上面的OS中的運行時情況相同。 在此圖中,操作系統是應用程序的范圍。 這里的應用程序的原因應該是對OS負責,OS可能不會虛擬化它們的代碼,或者允許崩潰。 如果操作系統總是阻止他們這樣做,那么無論應用程序做什么,它都是自我負責的。 但考慮一下驅動程序 ,它是操作系統必須允許崩潰的場景之一,因為這種應用程序被視為操作系統的一部分

    最后,讓我們在上圖中看到正確的圓圈 在這種情況下,應用程序本身就是宇宙。 有時,我們稱之為這種應用程序操作系統 如果操作系統從不允許加載和運行自定義代碼,那么它會自行完成所有操作。 即便它允許,在終止之后,內存無處可去,但硬件 所有可能需要的解除分配,都是在它終止之前。

    那么,你的程序對其他程序的關注程度是多少? 它對宇宙的關注程度是多少? 該項目的期望是如何完成其​​工作的? 這取決於你在乎什么

如果我要結束程序,為什么還需要刪除str?

因為你不想懶惰......

如果我要退出,我不在乎那個記憶是否會到達滿是獨角獸的土地,對吧?

不,我也不關心獨角獸的土地。 Arwen的土地是另一回事,然后我們可以削減它們的角度並使它們得到充分利用(我聽說它是​​一種很好的壯陽葯)。

這只是好習慣嗎?

這是一個很好的做法。

它有更深的后果嗎?

其他人必須在你之后清理。 也許你喜歡那樣,多年前我從父母的屋檐下搬出去了。

在代碼周圍放置while(1)循環結構而不刪除。 代碼復雜性並不重要。 內存泄漏與處理時間有關。

從調試的角度來看, 不釋放系統資源(文件句柄等)會導致更多重要且難以發現的錯誤。 重要的內存泄漏通常更容易診斷( 為什么我不能寫入此文件? )。 如果你開始使用線程,糟糕的風格將成為一個問題。

int main()
{

    while(1)
    { 
        char* str = new char[10];

        for(int i=0;i<5;i++)
        {
            //Do stuff with str
        }
    }

    delete[] str;
    return 0;
}

技術上 ,程序員不應該依賴操作系統做任何事情。 操作系統不需要以這種方式回收丟失的內存。

如果您確實編寫了刪除所有動態分配內存的代碼,那么您將來可以驗證代碼並讓其他人在更大的項目中使用它。

來源: 分配和GC神話 (PostScript警報!)

Allocation Myth 4: Non-garbage-collected programs should always
deallocate all memory they allocate.

The Truth: Omitted deallocations in frequently executed code cause
growing leaks. They are rarely acceptable. but Programs that retain
most allocated memory until program exit often perform better without
any intervening deallocation. Malloc is much easier to implement if
there is no free.

In most cases, deallocating memory just before program exit is
pointless. The OS will reclaim it anyway. Free will touch and page in
the dead objects; the OS won't.

Consequence: Be careful with "leak detectors" that count allocations.
Some "leaks" are good!
  • 我認為使用malloc / new而不調用free / delete是一種非常糟糕的做法。

  • 如果內存無論如何都會被回收,那么在你需要的時候明確解除分配會有什么危害呢?

  • 也許如果操作系統“回收”內存的速度比免費快,那么你會看到性能提升; 對於任何必須長時間保持運行的程序,此技術無法幫助您。

話雖如此,所以我建議你使用免費/刪除。


如果你養成這種習慣,誰會說你有一天不會意外地在某個地方應用這種方法呢?


在完成一個資源之后,應該總是釋放資源,無論是文件句柄/內存/互斥鎖。 通過養成這種習慣,在構建服務器時不會犯這種錯誤。 一些服務器預計將全天候運行。 在這些情況下,任何類型的泄漏都意味着您的服務器最終會耗盡該資源並以某種方式掛起/崩潰。 一個簡短的實用程序,你的泄漏並不是那么糟糕。 任何服務器,任何泄漏都是死亡。 幫自己一個忙。 自己清理干凈。 這是一個好習慣。


Think about your class 'A' having to deconstruct. If you don't call
'delete' on 'a', that destructor won't get called. Usually, that won't
really matter if the process ends anyway. But what if the destructor
has to release e.g. objects in a database? Flush a cache to a logfile?
Write a memory cache back to disk? **You see, it's not just 'good
practice' to delete objects, in some situations it is required**.

我還沒有提到的另一個原因是保持靜態和動態分析儀工具(例如valgrind或Coverity)的輸出更清潔和更安靜。 清零輸出,零內存泄漏或零報告問題意味着當彈出新內存時,更容易檢測和修復。

你永遠不知道如何使用或演化你的簡單例子。 最好從干凈和清爽開始。

更不用說如果你要申請C ++程序員的工作,你很有可能因為缺少刪除而無法通過面試。 首先 - 程序員通常不喜歡任何泄密(面試中的人肯定會是其中之一)而第二 - 大多數公司(我至少都在工作)都有“無泄漏”政策。 通常,您編寫的軟件應該運行一段時間,在運行中創建和銷毀對象。 在這樣的環境中,泄漏會導致災難......

我不會談論這個具體的例子,而是討論一般情況,所以通常顯式調用delete來取消分配內存是很重要的,因為(在C ++的情況下)你可能在析構函數中有一些你想要執行的代碼。 就像可能將一些數據寫入日志文件或將關閉信號發送到其他進程等。如果讓操作系統為您釋放內存,則析構函數中的代碼將不會被執行。

另一方面,大多數操作系統將在程序結束時釋放內存。 但是好的做法是自己解除分配,就像我在操作系統上面給出析構函數示例一樣,不會調用你的析構函數,這會在某些情況下造成不良行為!

我個人認為依靠操作系統釋放你的內存是不好的做法(即使它會這樣做),原因是如果你以后必須將你的代碼與更大的程序集成,你將花費數小時來追蹤和修復內存泄漏!

所以在離開之前打掃房間!

暫無
暫無

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

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