簡體   English   中英

Memory分配:棧還是堆?

[英]Memory allocation: Stack vs Heap?

我對Stack 與 Heap之間的 memory 分配基礎感到困惑。 根據標准定義(每個人都這么說),所有值類型都將分配到Stack中,而引用類型將 go 分配到Heap中。

現在考慮以下示例:

class MyClass
{
    int myInt = 0;    
    string myString = "Something";
}

class Program
{
    static void Main(string[] args)
    {
       MyClass m = new MyClass();
    }
}

現在,memory 分配將如何發生在 c# 中? MyClass的object(也就是m )會不會被完全分配到Heap? 也就是說, int myIntstring myString都會將 go 放到堆中?

或者,object會被分成兩部分,分別分配給memory兩個位置,即Stack和Heap?

您應該考慮將對象分配到何處的問題作為實現細節。 對象的位存儲在哪里對您來說並不重要。 對象是引用類型還是值類型可能很重要,但在開始優化垃圾收集行為之前,您不必擔心它將存儲在哪里。

雖然在當前實現中引用類型總是在堆上分配,但值類型可以在堆棧上分配——但不一定。 僅當值類型是未包含在引用類型中且未在寄存器中分配的未裝箱的非轉義本地或臨時變量時,才在堆棧上分配值類型。

  • 如果值類型是類的一部分(如您的示例中),它將最終出現在堆上。
  • 如果它被裝箱,它將最終在堆上。
  • 如果它在一個數組中,它將最終在堆上。
  • 如果它是一個靜態變量,它將最終在堆上。
  • 如果它被閉包捕獲,它將最終在堆上。
  • 如果它在迭代器或異步塊中使用,它將最終在堆上。
  • 如果它是由不安全或非托管代碼創建的,它可以分配在任何類型的數據結構中(不一定是堆棧或堆)。

有什么我錯過的嗎?

當然,如果我沒有鏈接到 Eric Lippert 關於該主題的帖子,我將是失職:

m在堆上分配,其中包括myInt 在堆棧上分配原始類型(和結構)的情況是在方法調用期間,它為堆棧上的局部變量分配空間(因為它更快)。 例如:

class MyClass
{
    int myInt = 0;

    string myString = "Something";

    void Foo(int x, int y) {
       int rv = x + y + myInt;
       myInt = 2^rv;
    }
}

rvxy都將在堆棧上。 myInt在堆上的某個地方(並且必須通過this指針訪問)。

“所有 VALUE 類型都將分配給堆棧”是非常非常錯誤的; 結構變量可以作為方法變量存在於堆棧中。 但是,類型上的字段與該類型一起存在 如果字段的聲明類型是類,則值作為該對象的一部分在堆上。 如果一個字段的聲明類型是一個結構,那么無論該結構在哪里,這些字段都是該結構的一部分。

甚至方法變量可以在堆上,如果它們被捕獲(lambda/anon-method),或者是(例如)迭代器塊的一部分。

stack是一塊內存,用於存儲local variablesparameters 隨着函數的進入和退出,堆棧在邏輯上會增長和縮小。

考慮以下方法:

public static int Factorial (int x)
{
    if (x == 0) 
    {
        return 1;
    }

    return x * Factorial (x - 1);
}

此方法是遞歸的,這意味着它會調用自身。 每次進入方法時,都會在堆棧上分配一個新的 int每次方法退出時,都會釋放 int


  • 堆是objects (即reference-type instances )駐留的一塊內存。 每當創建一個新對象時,它都會在堆上分配,並返回對該對象的引用。 在程序執行期間,隨着新對象的創建,堆開始填滿。 運行時有一個垃圾收集器,它會定期從堆中釋放對象,因此您的程序不會運行Out Of Memory 只要一個對象沒有被任何本身alive東西引用,它就有資格被釋放。
  • 堆還存儲static fields 與在堆上分配的對象(可以被垃圾回收)不同, these live until the application domain is torn downthese live until the application domain is torn down

考慮以下方法:

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder ref1 = new StringBuilder ("object1");
        Console.WriteLine (ref1);
        // The StringBuilder referenced by ref1 is now eligible for GC.

        StringBuilder ref2 = new StringBuilder ("object2");
        StringBuilder ref3 = ref2;
        // The StringBuilder referenced by ref2 is NOT yet eligible for GC.
        Console.WriteLine (ref3); // object2
    }
}    

在上面的例子中,我們首先創建了一個由變量 ref1 引用的 StringBuilder 對象,然后寫出它的內容。 然后該 StringBuilder 對象立即有資格進行垃圾收集,因為隨后沒有人使用它。 然后,我們創建另一個由變量 ref2 引用的 StringBuilder,並將該引用復制到 ref3。 即使 ref2 在那之后不再使用,ref3 也會保持同一個 StringBuilder 對象處於活動狀態——確保在我們使用完 ref3 之前它沒有資格被收集。

值類型實例(和對象引用)存在於聲明變量的任何地方。 如果實例被聲明為類類型中的字段或數組元素,則該實例位於堆上。

每次在其中創建對象時,它都會進入稱為堆的內存區域。 像 int 和 double 這樣的原始變量如果是局部方法變量,則分配在堆棧中;如果它們是成員變量,則分配在堆中。 在方法中,局部變量在調用方法時被壓入堆棧,而在方法調用完成時堆棧指針遞減。 在多線程應用程序中,每個線程都有自己的堆棧,但將共享同一個堆。 這就是為什么在你的代碼中應該小心避免堆空間中的任何並發訪問問題。 堆棧是線程安全的(每個線程都有自己的堆棧),但堆不是線程安全的,除非通過代碼同步保護。

這個鏈接也很有用http://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/

簡單的措施

值類型可以字符串在堆棧上,它是可以分配給一些未來主義數據結構的實現細節。

因此,最好了解值和引用類型的工作原理,值類型將按值復制,這​​意味着當您將值類型作為參數傳遞給 FUNCTION 時,它會被自然復制,這意味着您將擁有一個全新的副本.

引用類型是通過引用傳遞的(在未來的某些版本中,再次考慮到引用會再次存儲地址,它可能會存儲在其他一些數據結構中。)

所以在你的情況下

myInt 是一個 int,它被封裝在一個與引用類型無關的類中,因此它將與將存儲在“堆”上的類的實例相關聯。

我建議,你可以開始閱讀 ERIC LIPPERTS 寫的博客。

埃里克的博客

m 是對 MyClass 對象的引用,因此 m 存儲在主線程的堆棧中,但 MyClass 的對象存儲在堆中。 因此 myInt 和 myString 存儲在堆中。 請注意, m 只是一個引用(內存地址)並且位於主堆棧上。 當 m 被釋放時,GC 從堆中清除 MyClass 對象有關更多詳細信息,請閱讀本文的所有四個部分https://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net-第一部分/

根據標准定義(每個人都說),所有值類型都將分配到堆棧中,引用類型將進入堆中。

這是錯誤的。 只有本地(在函數的上下文中)值類型/值類型數組在堆棧上分配。 其他所有內容都分配在堆上。

暫無
暫無

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

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