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



12 Answers

堆:

  • 像堆一樣存儲在計算機RAM中。
  • 在堆棧上創建的變量將超出範圍並自動取消分配。
  • 與堆上的變量相比,分配要快得多。
  • 使用實際的堆棧數據結構實現。
  • 存儲本地數據,返回地址,用於參數傳遞。
  • 當使用過多的堆棧時(主要來自無限或太深的遞歸,非常大的分配),可能會出現堆棧溢出。
  • 可以在沒有指針的情況下使用在堆棧上創建的數據。
  • 如果您確切地知道在編譯之前需要分配多少數據並且它不是太大,您將使用堆棧。
  • 通常在程序啟動時已確定最大大小。

堆:

  • 像堆棧一樣存儲在計算機RAM中。
  • 在C ++中,必須手動銷毀堆上的變量,並且永遠不會超出範圍。 使用deletedelete[]free釋放數據。
  • 與堆棧上的變量相比,分配更慢。
  • 按需使用以分配程序使用的數據塊。
  • 當存在大量分配和解除分配時,可能會出現碎片。
  • 在C ++或C中,在堆上創建的數據將由指針指向,並分別用newmalloc分配。
  • 如果請求分配的緩衝區太大,可能會出現分配失敗。
  • 如果您不確切知道運行時需要多少數據,或者需要分配大量數據,則可以使用堆。
  • 負責內存洩漏。

例:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;
array vs what

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

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



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

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

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

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

堆棧

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

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

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

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

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




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

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

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

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

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

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

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




其他人已經很好地回答了廣泛的筆觸,所以我會提出一些細節。

  1. 堆棧和堆不必是單數。如果一個進程中有多個線程,則有多個堆棧的常見情況。在這種情況下,每個線程都有自己的堆棧。您也可以擁有多個堆,例如某些DLL配置可能會導致不同的堆分配不同的DLL,這就是為什麼釋放由不同庫分配的內存通常是個壞主意。

  2. 在C中,您可以通過使用alloca來獲得可變長度分配的好處,alloca在堆棧上分配,而不是在堆上分配的alloc。這個內存不會在你的return語句中存活,但它對臨時緩衝區很有用。

  3. 在Windows上創建一個巨大的臨時緩衝區並不是免費的。這是因為編譯器將生成每次輸入函數時調用的堆棧探測循環,以確保堆棧存在(因為Windows使用堆棧末尾的單個保護頁來檢測何時需要增加堆棧。如果你從堆棧的末尾訪問多個頁面的內存,你將崩潰)。例:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}



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

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

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

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




什麼是堆棧?

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

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

什麼是堆?

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

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

兩者一起

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

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

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

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

Java內存模型

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




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

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

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




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

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



  • 介紹

物理存儲器是存儲器單元的物理地址的範圍,其中應用程序或系統在執行期間存儲其數據,代碼等。內存管理表示通過將數據從物理內存交換到存儲設備然後在需要時返回物理內存來管理這些物理地址。 OS使用虛擬內存實現內存管理服務。作為C#應用程序開發人員,您無需編寫任何內存管理服務。 CLR使用底層操作系統內存管理服務為C#或任何其他針對CLR的高級語言提供內存模型。

圖4-1顯示了使用虛擬內存概念由OS抽象和管理的物理內存。虛擬內存是由OS管理的物理內存的抽象視圖。虛擬內存只是一系列虛擬地址,這些虛擬地址在需要時由CPU轉換為物理地址。

圖4-1。 CLR內存抽象

CLR使用操作內存服務為虛擬執行環境提供內存管理抽象層。CLR使用的抽象概念是AppDomain,線程,堆棧,堆存儲映射文件等。應用程序域(AppDomain)的概念為您的應用程序提供了一個獨立的執行環境。

  • CLR和OS之間的內存交互

通過在調試以下C#應用程序時查看堆棧跟踪,使用WinDbg,您將看到CLR如何使用底層操作系統內存管理服務(例如,來自KERNEL32.dll的HeapFree方法,來自ntdll.dll的RtlpFreeHeap方法)來實現自己的記憶模型:

using System;
namespace CH_04
{
    class Program
    {
        static void Main(string[] args)
        {
            Book book = new Book();
            Console.ReadLine();
        }
    }

    public class Book
    {
        public void Print() { Console.WriteLine(ToString()); }
    }
}

程序的編譯程序集被加載到WinDbg中以開始調試。您可以使用以下命令初始化調試會話:

0:000> sxe ld clrjit

0:000> g

0:000> .loadby sos clr

0:000> .load C:\ Windows \ Microsoft.NET \ Framework \ v4.0.30319 \ sos.dll

然後,使用!bpmd命令在Program類的Main方法中設置斷點:

0:000>!bpmd CH_04.exe CH_04.Program.Main

要繼續執行並在斷點處中斷,請執行g命令:

0:000> g

當執行在斷點處中斷時,使用!eestack命令查看為當前進程運行的所有線程的堆棧跟踪詳細信息。以下輸出顯示了為應用程序CH_04.exe運行的所有線程的堆棧跟踪:

0:000>!eestack

線程0

當前幀:(MethodDesc 00233800 +0 CH_04.Program.Main(System.String []))

ChildEBP RetAddr Caller,Callee

0022ed24 5faf21db clr!CallDescrWorker + 0x33

/ trace刪除 /

0022f218 77712d68 ntdll!RtlFreeHeap + 0x142,調用ntdll!RtlpFreeHeap

0022f238 771df1ac KERNEL32!HeapFree + 0x14,調用ntdll!RtlFreeHeap

0022f24c 5fb4c036 clr!EEHeapFree + 0x36,調用KERNEL32!HeapFree

0022f260 5fb4c09d clr!EEHeapFreeInProcessHeap + 0x24,調用clr!EEHeapFree

0022f274 5fb4c06d clr!operator delete [] + 0x30,調用clr!EEHeapFreeInProcessHeap / trace刪除 /

0022f4d0 7771316f ntdll!RtlpFreeHeap + 0xb7a,調用ntdll!_SEH_epilog4

0022f4d4 77712d68 ntdll!RtlFreeHeap + 0x142,調用ntdll!RtlpFreeHeap

0022f4f4 771df1ac KERNEL32!HeapFree + 0x14,調用ntdll!RtlFreeHeap

/ trace刪除 /

此堆棧跟踪指示CLR使用OS內存管理服務來實現其自己的內存模型。.NET中的任何內存操作都通過CLR內存層傳輸到OS內存管理層。

圖4-2說明了CLR在運行時使用的典型C#應用程序內存模型。

圖4-2。典型的C#應用程序內存模型

CLR內存模型與OS內存管理服務緊密耦合。要了解CLR內存模型,了解底層操作系統內存模型非常重要。了解物理內存地址空間如何被抽像到虛擬內存地址空間,用戶應用程序和系統應用程序使用虛擬地址空間的方式,虛擬到物理地址映射如何工作,內存如何也是至關重要的。映射文件有效,等等。這些背景知識將提高您對CLR內存模型概念的掌握,包括AppDomain,堆棧和堆。

有關更多信息,請參閱本書:

C#解構:了解C#如何在.NET Framework上運行

本書+ ClrViaC#+ Windows Internals是已知的.net框架的深入資源和與OS的關係的優秀資源。




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

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

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

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

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




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




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

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

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

有趣的說明:

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

反饋很好。




Related