memory-management stack不同 - 堆棧和堆的內容和位置是什麼?





heap array (21)


簡單地說,堆棧是創建局部變量的地方。此外,每次調用子程序時,程序計數器(指向下一個機器指令的指針)和任何重要的寄存器,有時參數都會被壓入堆棧。然後子程序中的任何局部變量都被壓入堆棧(並從那裡使用)。當子程序結束時,所有東西都會從堆棧中彈出。PC和寄存器數據會隨著彈出而被放回原位,因此您的程序可以順利進行。

堆是內存區域動態內存分配由(顯式“新”或“分配”調用)組成。它是一種特殊的數據結構,可以跟踪不同大小的內存塊及其分配狀態。

在“經典”系統中,RAM被佈置成使得堆棧指針從內存的底部開始,堆指針從頂部開始,並且它們朝向彼此增長。如果它們重疊,則表示RAM不足。但這不適用於現代多線程操作系統。每個線程都必須有自己的堆棧,這些堆棧可以動態創建。

編程語言書籍解釋了在堆棧上創建了值類型,並且在堆上創建了引用類型,而沒有解釋這兩者是什麼。 我還沒有看到對此的明確解釋。 我理解堆棧是什麼。 但,

  • 它們在哪里和它們(物理上在真實計算機的記憶中)?
  • 它們在多大程度上受操作系統或語言運行時控制?
  • 它們的範圍是什麼?
  • 是什麼決定了它們的大小?
  • 是什麼讓一個更快?



堆棧是作為執行線程的臨時空間留出的內存。 調用函數時,在堆棧頂部保留一個塊,用於本地變量和一些簿記數據。 當該函數返回時,該塊將變為未使用狀態,並可在下次調用函數時使用。 堆棧始終以LIFO(後進先出)順序保留; 最近保留的塊始終是要釋放的下一個塊。 這使得跟踪堆棧非常簡單; 從堆棧中釋放塊只不過是調整一個指針。

堆是為動態分配留出的內存。 與堆棧不同,堆中的塊的分配和釋放沒有強制模式; 您可以隨時分配一個塊並隨時釋放它。 這使得在任何給定時間跟踪堆的哪些部分被分配或釋放變得更加複雜; 有許多自定義堆分配器可用於調整不同使用模式的堆性能。

每個線程都有一個堆棧,而應用程序通常只有一個堆(儘管為不同類型的分配設置多個堆並不罕見)。

直接回答您的問題:

它們在多大程度上受操作系統或語言運行時控制?

操作系統在創建線程時為每個系統級線程分配堆棧。 通常,語言運行庫調用OS來為應用程序分配堆。

它們的範圍是什麼?

堆棧附加到一個線程,因此當線程退出堆棧時將被回收。 堆通常在應用程序啟動時由運行時分配,並在應用程序(技術過程)退出時回收。

是什麼決定了它們的大小?

創建線程時設置堆棧的大小。 堆的大小在應用程序啟動時設置,但可以在需要空間時增長(分配器從操作系統請求更多內存)。

是什麼讓一個更快?

堆棧更快,因為訪問模式使得從中分配和釋放內存變得微不足道(指針/整數簡單地遞增或遞減),而堆在分配或釋放中涉及更複雜的簿記。 此外,堆棧中的每個字節都經常被頻繁地重用,這意味著它往往被映射到處理器的緩存,使其非常快。 堆的另一個性能影響是堆(主要是全局資源)通常必須是多線程安全的,即每個分配和釋放需要 - 通常 - 與程序中的“所有”其他堆訪問同步。

明確的示範:
圖片來源: vikashazrati.wordpress.com




最重要的一點是堆和堆棧是可以分配內存的通用術語。 它們可以以多種不同的方式實現,並且這些術語適用於基本概念。

  • 在一堆物品中,物品按照它們放置在那裡的順序一個在另一個上面,你只能移除頂部的物品(不會翻倒整個物品)。

    堆棧的簡單性在於您不需要維護包含已分配內存的每個部分的記錄的表; 您需要的唯一狀態信息是指向堆棧末尾的單個指針。 要分配和取消分配,只需遞增和遞減該單個指針即可。 注意:有時可以實現堆棧從一部分內存的頂部開始並向下延伸而不是向上擴展。

  • 在堆中,對項目的放置方式沒有特定的順序。 您可以按任意順序進入和移除商品,因為沒有明確的“頂部”商品。

    堆分配需要維護已分配內存和不分配內存的完整記錄,以及減少碎片的一些開銷維護,找到足以滿足請求大小的連續內存段,等等。 內存可以在任何時候釋放,留下空閒空間。 有時,內存分配器將執行維護任務,例如通過移動已分配的內存來對內存進行碎片整理,或者進行垃圾收集 - 在內存不再佔用範圍並在解除分配時在運行時進行識別。

這些圖像應該可以很好地描述在堆棧和堆中分配和釋放內存的兩種方法。 百勝!

  • 它們在多大程度上受操作系統或語言運行時控制?

    如上所述,堆和堆棧是通用術語,可以通過多種方式實現。 計算機程序通常具有稱為調用堆棧的堆棧 ,其存儲與當前函數相關的信息,例如指向調用它的任何函數的指針,以及任何局部變量。 因為函數調用其他函數然後返回,所以堆棧增大和縮小以保持來自調用堆棧中的函數的信息。 程序實際上沒有對它進行運行時控制; 它取決於編程語言,操作系統甚至系統架構。

    堆是一個通用術語,用於動態和隨機分配的任何內存; 即無序。 內存通常由OS分配,應用程序調用API函數來執行此分配。 管理動態分配的內存需要相當大的開銷,這通常由操作系統處理。

  • 它們的範圍是什麼?

    調用堆棧是一種低級概念,它與編程意義上的“範圍”無關。 如果您反彙編某些代碼,您將看到對堆棧部分的相對指針樣式引用,但就更高級別的語言而言,該語言強加了自己的範圍規則。 但是,堆棧的一個重要方面是,一旦函數返回,該函數的任何本地函數都會立即從堆棧中釋放出來。 考慮到編程語言的工作原理,它的工作方式與預期的方式相同。 在堆中,它也很難定義。 範圍是操作系統公開的內容,但是您的編程語言可能會添加有關應用程序中“範圍”的規則。 處理器體系結構和OS使用虛擬尋址,處理器將虛擬尋址轉換為物理地址,並存在頁面錯誤等。它們跟踪哪些頁面屬於哪些應用程序。 但是,您永遠不必擔心這一點,因為您只需使用編程語言用於分配和釋放內存的任何方法,並檢查錯誤(如果分配/釋放因任何原因失敗)。

  • 是什麼決定了它們的大小?

    同樣,它取決於語言,編譯器,操作系統和體系結構。 堆棧通常是預先分配的,因為根據定義它必須是連續的內存(更多內容在最後一段中)。 語言編譯器或OS確定其大小。 您不會在堆棧上存儲大量數據,因此除非出現不必要的無限遞歸(因此,“堆棧溢出”)或其他異常編程決策,否則它應該永遠不會被完全使用。

    堆是可以動態分配的任何東西的通用術語。 根據您看待它的方式,它會不斷變化大小。 在現代處理器和操作系統中,它的工作方式無論如何都是非常抽象的,所以你通常不需要擔心它如何深入工作,除了(在它允許你的語言中)你不能使用的內存你尚未分配或已釋放的記憶。

  • 是什麼讓一個更快?

    堆棧更快,因為所有可用內存始終是連續的。 不需要維護所有可用內存段的列表,只需要指向當前堆棧頂部的單個指針。 為此,編譯器通常將此指針存儲在一個特殊的快速register中。 更重要的是,堆棧上的後續操作通常集中在非常靠近的內存區域,這非常低的水平有利於處理器片上高速緩存的優化。




好吧,簡單而言之,它們意味著有序不是有序 ......!

堆棧:在堆棧項目中,事物處於彼此的頂部,意味著要更快,更有效地處理!...

所以總有一個索引指向特定項目,處理也會更快,項目之間也有關係!...

:沒有訂單,處理速度會慢,價值混亂,沒有特定的訂單或索引......有隨機的,它們之間沒有關係...所以執行和使用時間可能會有所不同......

我還創建了下面的圖像,以顯示它們的外觀:




很多答案都是正確的概念,但我們必須注意硬件(即微處理器)需要一個堆棧來允許調用子程序(彙編語言中的CALL ......)。(OOP人會稱之為方法

在堆棧上保存返回地址並調用→push / ret→pop直接在硬件中管理。

您可以使用堆棧傳遞參數..即使它比使用寄存器慢(微處理器大師會說或者20世紀80年代的BIOS書籍......)

  • 沒有堆棧,沒有微處理器可以工 (我們無法想像一個程序,即使是彙編語言,沒有子程序/函數​​)
  • 沒有堆它可以。(彙編語言程序可以在沒有,因為堆是OS概念,而不是malloc,即OS / Lib調用。

堆棧使用速度更快:

  • 硬件,甚至推/彈都非常有效。
  • malloc需要進入內核模式,使用執行某些代碼的鎖/信號量(或其他同步原語)並管理跟踪分配所需的一些結構。



我想很多其他人在這件事上給了你大部分正確答案。

然而,遺漏的一個細節是“堆”實際上可能被稱為“免費商店”。這種區別的原因是原始的免費存儲是使用稱為“二項式堆”的數據結構實現的。因此,從malloc()/ free()的早期實現中分配是從堆中分配的。然而,在這個現代,大多數免費商店都使用非二維堆的非常精細的數據結構來實現。




堆棧是內存的一部分,可以通過幾個關鍵的彙編語言指令來操作,例如'pop'(從堆棧中刪除並返回一個值)和'push'(將值推送到堆棧),還可以調用(調用子程序 - 這會將地址推回到堆棧中)並返回(從子程序返回 - 這會將地址彈出堆棧並跳轉到它)。它是堆棧指針寄存器下面的內存區域,可以根據需要進行設置。堆棧還用於將參數傳遞給子例程,也用於在調用子例程之前保留寄存器中的值。

堆是操作系統給應用程序的內存的一部分,通常通過類似malloc的系統調用。在現代操作系統上,此內存是一組只有調用進程才能訪問的頁面。

堆棧的大小在運行時確定,並且通常在程序啟動後不會增長。在C程序中,堆棧需要足夠大以容納每個函數中聲明的每個變量。堆將根據需要動態增長,但操作系統最終會進行調用(它通常會使堆積增長超過malloc請求的值,因此至少某些未來的malloc將不需要返回到內核獲得更多內存。這種行為通常可以自定義)

因為你在啟動程序之前已經分配了堆棧,所以在使用堆棧之前你永遠不需要malloc,所以這是一個小優勢。在實踐中,很難預測具有虛擬內存子系統的現代操作系統的速度和速度會有多快,因為頁面的實現方式和存儲位置是實現細節。




我有一些東西要與你分享,雖然已經寫了重點。

  • 非常快速的訪問。
  • 存儲在RAM中。
  • 這裡加載函數調用以及傳遞的局部變量和函數參數。
  • 當程序超出範圍時,會自動釋放空間。
  • 存儲在順序存儲器中。

  • 相對於Stack而言訪問速度較慢。
  • 存儲在RAM中。
  • 動態創建的變量存儲在此處,以後需要在使用後釋放分配的內存。
  • 存儲在內存分配的任何位置,始終由指針訪問。

有趣的說明:

  • 如果函數調用已存儲在堆中,則會產生兩個混亂點:
    1. 由於堆棧中的順序存儲,執行速度更快。堆中存儲會導致大量時間消耗,從而導致整個程序執行速度變慢。
    2. 如果函數存儲在堆中(指針指向凌亂的存儲),則無法返回到調用者地址(由於內存中的順序存儲,該堆棧會產生)。

反饋很好。




幾分錢:我認為,繪製內存圖形化更簡單:


箭頭 - 顯示增長堆棧和堆的位置,進程堆棧大小有限制,在OS中定義,線程堆棧大小通常由線程創建API中的參數限制。堆通常限制為進程最大虛擬內存大小,例如32位2-4 GB。

這麼簡單的方法:進程堆通常用於進程和內部的所有線程,在常見情況下用於內存分配,例如malloc()

堆棧是快速存儲器,用於存儲常見情況函數返回指針和變量,作為函數調用中的參數處理,本地函數變量。




來自WikiAnwser。

當函數或方法調用另一個函數,該函數又調用另一個函數等時,所有這些函數的執行將保持掛起,直到最後一個函數返回其值。

這個掛起函數調用鍊是堆棧,因為堆棧中的元素(函數調用)彼此依賴。

在異常處理和線程執行中,堆棧很重要。

堆只是程序用來存儲變量的內存。堆的元素(變量)彼此之間沒有依賴關係,並且可以隨時隨機訪問。




由於一些答案被挑剔,我將貢獻我的蟎蟲。

令人驚訝的是,沒有人提到多個(即與運行的OS級線程的數量無關)調用堆棧不僅可以在外來語言(PostScript)或平台(Intel Itanium)中找到,還可以在fibers綠色線程中找到coroutines一些實現。

纖維,綠線和協同程序在很多方面都很相似,這導致了很多混亂。光纖和綠色線程之間的區別在於前者使用協作式多任務處理,而後者可能具有協作式或搶占式(或甚至兩者)。有關纖維和協同程序之間的區別,請參見here

在任何情況下,光纖,綠色線程和協同程序的目的是同時執行多個函數,但不是並行執行(在這個OS級別的線程中參見此問題,以區分),從而相互傳遞控制權以有組織的方式。

使用光纖,綠色線程或協程時,每個函數通常都有一個單獨的堆棧。 (從技術上講,不僅僅是一個堆棧,而是整個執行上下文是每個函數。最重要的是,CPU寄存器。)對於每個線程,存在與並發運行函數一樣多的堆棧,並且線程在執行每個函數之間切換根據你的程序的邏輯。當一個函數運行到它的結尾時,它的堆棧就會被銷毀。因此,堆棧的數量和生命週期是動態的,並不是由OS級線程的數量決定的!

請注意,我說“ 每個函數通常有一個單獨的堆棧”。有倆都stackful無堆疊 couroutines的實現。最值得注意的stackful C ++實現是Boost.Coroutine微軟PPLasync/await。(然而,C ++的可恢復函數(又名“ asyncawait”),它們被提議用於C ++ 17,可能會使用無堆棧協程。)

纖維對C ++標準庫的提議即將發布。還有一些第三方libraries。綠色線程在Python和Ruby等語言中非常流行。




你可以用堆棧做一些有趣的事情。例如,你有像alloca這樣的函數(假設你可以通過關於它的使用的大量警告),這是一種malloc,它專門使用堆棧而不是堆來存儲。

也就是說,基於堆棧的內存錯誤是我經歷過的最糟糕的錯誤。如果使用堆內存,並且超出了已分配塊的範圍,則有可能觸發段故障。(不是100%:你的塊可能偶然與你先前分配的另一個塊連續。)但是由於在堆棧上創建的變量總是彼此連續,因此寫出越界可以改變另一個變量的值。我了解到每當我覺得我的程序已經停止遵守邏輯定律時,它可能就是緩衝區溢出。




  • 非常快速的訪問
  • 不必顯式取消分配變量
  • 空間由CPU有效管理,內存不會碎片化
  • 僅限局部變量
  • 堆棧大小限制(取決於操作系統)
  • 變量無法調整大小

  • 可以全局訪問變量
  • 內存大小沒有限制
  • (相對)訪問速度較慢
  • 無法保證有效利用空間,隨著時間的推移,內存可能會隨著內存塊的分配而變得碎片化,然後被釋放
  • 你必須管理內存(你負責分配和釋放變量)
  • 可以使用realloc()調整變量大小



其他人直接回答了你的問題,但在嘗試理解堆棧和堆時,我認為考慮傳統UNIX進程的內存佈局(沒有線程和mmap()基於分配器)是有幫助的。“ 內存管理術語表”網頁提供了此內存佈局的圖表。

堆棧和堆傳統上位於進程的虛擬地址空間的兩端。堆棧在訪問時自動增長,最大內核設置的大小(可以調整setrlimit(RLIMIT_STACK, ...))。當內存分配器調用brk()sbrk()系統調用時,堆會增長,將更多頁面的物理內存映射到進程的虛擬地址空間。

在沒有虛擬內存的系統中,例如某些嵌入式系統,通常會應用相同的基本佈局,但堆棧和堆的大小是固定的。但是,在其他嵌入式系統(例如基於Microchip PIC單片機的系統)中,程序堆棧是一個單獨的內存塊,無法通過數據移動指令尋址,只能通過程序流指令間接修改或讀取(調用,返回等)。其他架構(如Intel Itanium處理器)具有多個堆棧。從這個意義上說,堆棧是CPU架構的一個元素。




在以下C#代碼中

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

以下是內存的管理方式

只要函數調用進入堆棧,只需要持續的Local Variables 。 堆用於變量,我們事先並不知道它們的生命週期,但我們希望它們可以持續一段時間。 在大多數語言中,如果我們想要將它存儲在堆棧中,那麼在編譯時我們知道變量的大小是至關重要的。

對象(在我們更新它們時大小不同)會在堆上進行,因為我們在創建時不知道它們將持續多長時間。 在許多語言中,堆被垃圾收集以查找不再具有任何引用的對象(例如cls1對象)。

在Java中,大多數對象直接進入堆。 在像C / C ++這樣的語言中,當你不處理指針時,結構和類通常可以保留在堆棧中。

更多信息可以在這裡找到:

堆棧和堆內存分配之間的區別«timmurphy.org

和這裡:

在堆棧和堆上創建對象

本文是上圖的源代碼: 六個重要的.NET概念:堆棧,堆,值類型,引用類型,裝箱和拆箱 - CodeProject

但請注意,它可能包含一些不准確之處。




堆棧當你調用一個函數時,該函數的參數加上一些其他開銷被放在堆棧上。 一些信息(例如返回的地方)也存儲在那裡。 在函數內部聲明變量時,該變量也會在堆棧中分配。

取消分配堆棧非常簡單,因為您總是按照分配的相反順序解除分配。 輸入函數時會添加堆棧內容,退出時會刪除相應的數據。 這意味著您傾向於保持在堆棧的一個小區域內,除非您調用許多調用許多其他函數的函數(或創建遞歸解決方案)。

堆堆是一個通用名稱,用於放置您即時創建的數據。 如果您不知道程序將要創建多少太空飛船,您可能會使用新的(或malloc或等效的)運算符來創建每個太空飛船。 這種分配會持續一段時間,所以很可能我們會以與創建它們不同的順序釋放事物。

因此,堆要復雜得多,因為最終存在未使用的內存區域與內存被分段的塊交織。 找到所需大小的空閒內存是一個難題。 這就是應該避免堆的原因(雖然它仍然經常使用)。

實現堆棧和堆的實現通常都是運行時/操作系統。 通常,性能至關重要的遊戲和其他應用程序會創建自己的內存解決方案,從堆中獲取大量內存,然後在內部將其清除,以避免依賴操作系統獲取內存。

這只有在你的內存使用量與標準大不相同的情況下才有用 - 例如,對於你在一個巨大的操作中加載一個級別並且可以在另一個巨大的操作中丟掉所有內存的遊戲。

內存中的物理位置由於虛擬內存技術使您的程序認為您可以訪問物理數據位於其他位置的某個地址(甚至在硬盤上!),因此與您的想法相關性較低。 隨著調用樹的深入,您獲得的堆棧地址會逐漸增加。 堆的地址是不可預測的(即特定於implimentation),坦率地說並不重要。




澄清一下 , 這個答案有不正確的信息( thomas在評論後修正了他的答案,很酷:))。 其他答案只是避免解釋靜態分配的含義。 因此,我將解釋三種主要的分配形式以及它們通常如何與下面的堆,堆棧和數據段相關聯。 我還將在C / C ++和Python中展示一些示例,以幫助人們理解。

“靜態”(AKA靜態分配)變量未在堆棧上分配。 不要這麼認為 - 很多人只是因為“靜態”聽起來很像“堆疊”。 它們實際上既不存在於堆棧中,也不存在於堆中。 這是所謂的數據段的一部分

但是,通常最好考慮“ 範圍 ”和“ 生命週期 ”而不是“堆棧”和“堆積”。

範圍是指代碼的哪些部分可以訪問變量。 通常我們會考慮局部範圍 (只能通過當前函數訪問)與全局範圍 (可以在任何地方訪問),儘管範圍可能變得更加複雜。

生命週期是指在程序執行期間分配和取消分配變量的時間。通常我們會想到靜態分配(變量將持續整個程序的持續時間,使其對於在多個函數調用中存儲相同的信息很有用)與自動分配(變量僅在單個函數調用期間持續存在,使其對於存儲僅在函數期間使用的信息,並且一旦完成就可以丟棄)與動態分配(變量的持續時間在運行時定義,而不是像靜態或自動一樣的編譯時)。

雖然大多數編譯器和解釋器在使用堆棧,堆等方麵類似地實現了這種行為,但只要行為正確,編譯器有時可能會破壞這些約定。例如,由於優化,局部變量可能只存在於寄存器中或被完全刪除,即使堆棧中存在大多數局部變量。正如在一些註釋中指出的那樣,你可以自由地實現一個甚至不使用堆棧或堆的編譯器,而是使用其他一些存儲機制(很少做,因為堆棧和堆很適合這個)。

我將提供一些簡單的帶註釋的C代碼來說明所有這些。學習的最佳方法是在調試器下運行程序並觀察行為。如果您更喜歡閱讀python,請跳到答案結尾:)

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

為什麼區分生命週期和範圍很重要的一個特別尖銳的例子是變量可以具有局部範圍但是具有靜態生命週期 - 例如,上面的代碼示例中的“someLocalStaticVariable”。這些變量可以使我們共同但非正式的命名習慣非常混亂。例如,當我們說“ 本地 ”時,我們通常表示“ 本地範圍自動分配變量 ”,當我們說全局時,我們通常表示“ 全局範圍的靜態分配變量 ”。不幸的是,當涉及到“ 文件範圍靜態分配變量 ” 這樣的事情時,很多人只會說...“ 嗯??? ”。

C / C ++中的一些語法選擇加劇了這個問題 - 例如,由於下面顯示的語法,許多人認為全局變量不是“靜態的”。

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

請注意,在上面的聲明中放置關鍵字“static”可以防止var2具有全局範圍。然而,全局var1具有靜態分配。這不直觀!出於這個原因,我嘗試在描述範圍時從不使用“靜態”一詞,而是說“文件”或“文件限制”範圍。然而,許多人使用短語“靜態”或“靜態範圍”來描述只能從一個代碼文件訪問的變量。在生命週期的上下文中,“靜態” 總是表示變量在程序啟動時分配,並在程序退出時釋放。

有些人認為這些概念是特定於C / C ++的。 他們不是。 例如,下面的Python示例說明了所有三種類型的分配(在解釋語言中可能存在一些細微差別,我將不會在這裡進行討論)。

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones



(我已經從另一個或多或少是這個問題的問題中提出了這個答案。)

您的問題的答案是特定於實現的,並且可能因編譯器和處理器體系結構而異。 但是,這是一個簡化的解釋。

  • 堆棧和堆都是從底層操作系統分配的內存區域(通常是按需映射到物理內存的虛擬內存)。
  • 在多線程環境中,每個線程都有自己完全獨立的堆棧,但它們將共享堆。 必須在堆上控制並發訪問,並且不能在堆棧上進行訪問。

  • 堆包含已使用和可用塊的鏈接列表。 通過從其中一個空閒塊創建合適的塊,可以滿足堆上的新分配(通過newmalloc )。 這需要更新堆上的塊列表。 有關堆上塊的元信息也經常存儲在堆中,位於每個塊的前面。
  • 隨著堆增長,新塊通常從較低地址分配給較高地址。 因此,您可以將堆視為一堆內存塊,這些內存塊在分配內存時會增大。 如果堆對於分配而言太小,則通常可以通過從底層操​​作系統獲取更多內存來增加大小。
  • 分配和解除分配許多小塊可能使堆處於這樣的狀態:在所使用的塊之間散佈有許多小的空閒塊。 分配大塊的請求可能失敗,因為即使空閒塊的組合大小足夠大,也沒有任何空閒塊足夠大以滿足分配請求。 這稱為堆碎片
  • 當釋放與空閒塊相鄰的使用塊時,可以將新的空閒塊與相鄰的空閒塊合併以創建更大的空閒塊,從而有效地減少堆的碎片。

堆棧

  • 堆棧通常與CPU上的特殊寄存器緊密串聯,命名為堆棧指針 。 最初,堆棧指針指向堆棧的頂部(堆棧中的最高地址)。
  • CPU具有值推入堆棧並從堆棧中彈回的特殊指令。 每次推送都將值存儲在堆棧指針的當前位置,並減少堆棧指針。 pop會檢索堆棧指針指向的值,然後增加堆棧指針(不要因為堆棧添加減少堆棧指針並刪除增加它這一事實而感到困惑。請記住堆棧增長到底部)。 存儲和檢索的值是CPU寄存器的值。
  • 當調用函數時,CPU使用特殊指令來推送當前指令指針 ,即在堆棧上執行的代碼的地址。 然後CPU通過將指令指針設置為被調用函數的地址來跳轉到該函數。 稍後,當函數返回時,從堆棧中彈出舊的指令指針,並在調用函數後立即執行代碼。
  • 輸入函數時,堆棧指針會減少,以便在堆棧上為本地(自動)變量分配更多空間。 如果函數有一個本地32位變量,則在堆棧上留出四個字節。 當函數返回時,堆棧指針被移回以釋放分配的區域。
  • 如果函數有參數,則在調用函數之前將這些參數壓入堆棧。 然後,函數中的代碼能夠從當前堆棧指針向上導航堆棧以找到這些值。
  • 嵌套函數調用就像魅力一樣。 每個新調用都將分配函數參數,返回地址和局部變量的空間,這些激活記錄可以堆疊用於嵌套調用,並在函數返回時以正確的方式展開。
  • 由於堆棧是有限的內存塊,因此可以通過調用太多嵌套函數和/或為局部變量分配太多空間來導致堆棧溢出 。 通常,用於堆棧的存儲區域的設置方式是在堆棧的底部(最低地址)下寫入將觸發CPU中的陷阱或異常。 然後,運行時可以捕獲此異常情況並將其轉換為某種堆棧溢出異常。

可以在堆而不是堆棧上分配函數嗎?

不,函數的激活記錄(即本地或自動變量)在堆棧上分配,不僅用於存儲這些變量,還用於跟踪嵌套函數調用。

如何管理堆實際上取決於運行時環境。 C使用malloc而C ++使用new ,但許多其他語言都有垃圾收集。

但是,堆棧是與處理器架構緊密相關的更低級別的功能。 當沒有足夠的空間時增加堆不是太難,因為它可以在處理堆的庫調用中實現。 但是,堆棧的增長通常是不可能的,因為只有在為時已晚時才發現堆棧溢出; 並且關閉執行線程是唯一可行的選擇。




虛擬內存中每個進程的堆棧數據




什麼是堆棧?

堆疊是一堆物體,通常是整齊排列的物體。

計算體系結構中的堆棧是存儲器區域,其中以後進先出的方式添加或移除數據。
在多線程應用程序中,每個線程都有自己的堆棧。

什麼是堆?

堆是隨意堆積的不整齊的東西。

在計算體系結構中,堆是動態分配的內存區域,由操作系統或內存管理器庫自動管理。
在程序執行期間,會定期分配,釋放和重新調整堆上的內存,這可能會導致稱為碎片的問題。
當內存對像被分配時,它們之間的空間太小而無法容納額外的內存對象,就會發生碎片。
最終結果是堆空間的百分比不可用於進一步的內存分配。

兩者一起

在多線程應用程序中,每個線程都有自己的堆棧。但是,所有不同的線程都將共享堆。
因為不同的線程在多線程應用程序中共享堆,這也意味著線程之間必須有一些協調,這樣它們就不會嘗試訪問和操作堆中的同一塊內存。同一時間。

哪個更快 - 堆棧還是堆?為什麼?

堆棧比堆快得多。
這是因為在堆棧上分配內存的方式。
在堆棧上分配內存就像向上移動堆棧指針一樣簡單。

對於剛接觸編程的人來說,使用堆棧可能是個好主意,因為它更容易。
由於堆棧很小,因此當您確切知道數據需要多少內存時,或者如果您知道數據的大小非常小時,則需要使用它。
當您知道數據需要大量內存時,最好使用堆,或者您不確定需要多少內存(例如動態數組)。

Java內存模型

堆棧是存儲局部變量(包括方法參數)的存儲區域。對於對像變量,這些僅僅是對堆上實際對象的引用(指針)。
每次實例化對象時,都會留出一大堆堆內存來保存該對象的數據(狀態)。由於對象可以包含其他對象,因此某些數據實際上可以保存對這些嵌套對象的引用。




一種方法是開始註釋掉代碼並檢查錯誤是否仍然發生。 是的,這是繁瑣和基本的,但如果你知道bug在哪裡可能會有所幫助。

它崩潰的地方就是為什麼它會崩潰等等。





memory-management language-agnostic stack heap