88

我对Stack 与 Heap之间的内存分配基础知识感到困惑。根据标准定义(每个人都说),所有值类型都将分配到Stack中,而引用类型将进入Heap中。

现在考虑以下示例:

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

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

现在,c# 中的内存分配将如何发生?MyClass(即)的对象m会完全分配给 Heap 吗?也就是说,int myInt两者string myString都会进入堆?

或者,对象将被分成两部分,并被分配到两个内存位置,即堆栈和堆?

4

9 回答 9

71

您应该考虑将对象分配到何处的问题作为实现细节。对象的位存储在哪里对您来说并不重要。对象是引用类型还是值类型可能很重要,但在开始优化垃圾收集行为之前,您不必担心它将存储在哪里。

虽然在当前实现中引用类型总是在堆上分配,但值类型可以在堆栈上分配——但不一定。仅当值类型是未包含在引用类型中且未在寄存器中分配的未装箱的非转义本地或临时变量时,才在堆栈上分配值类型。

  • 如果值类型是类的一部分(如您的示例中),它将最终出现在堆上。
  • 如果它被装箱,它将最终在堆上。
  • 如果它在一个数组中,它将最终在堆上。
  • 如果它是一个静态变量,它将最终在堆上。
  • 如果它被闭包捕获,它将最终在堆上。
  • 如果它在迭代器或异步块中使用,它将最终在堆上。
  • 如果它是由不安全或非托管代码创建的,它可以分配在任何类型的数据结构中(不一定是堆栈或堆)。

有什么我错过的吗?

当然,如果我没有链接到 Eric Lippert 关于该主题的帖子,我将是失职:

于 2010-12-20T06:09:11.013 回答
58

m在堆上分配,其中包括myInt. 在堆栈上分配原始类型(和结构)的情况是在方法调用期间,它为堆栈上的局部变量分配空间(因为它更快)。例如:

class MyClass
{
    int myInt = 0;

    string myString = "Something";

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

rv, x,y都将在堆栈上。myInt在堆上的某个地方(并且必须通过this指针访问)。

于 2010-12-20T06:08:55.523 回答
24

“所有 VALUE 类型都将分配给堆栈”是非常非常错误的;结构变量可以作为方法变量存在于堆栈中。但是,类型上的字段与该类型一起存在。如果字段的声明类型是类,则值作为该对象的一部分在堆上。如果一个字段的声明类型是一个结构,那么无论该结构在哪里,这些字段都是该结构的一部分。

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

于 2010-12-20T06:09:22.877 回答
2

stack是用于存储local variables和的内存块parameters。随着函数的进入和退出,堆栈在逻辑上会增长和缩小。

考虑以下方法:

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 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 之前它没有资格被收集。

值类型实例(和对象引用)存在于声明变量的任何地方。如果实例被声明为类类型中的字段或数组元素,则该实例位于堆上。

于 2018-01-28T18:22:26.180 回答
1

简单的措施

值类型可以字符串在堆栈上,它是可以分配给一些未来主义数据结构的实现细节。

因此,最好了解值和引用类型的工作原理,值类型将按值复制,这​​意味着当您将值类型作为参数传递给 FUNCTION 时,它会被自然复制,这意味着您将拥有一个全新的副本.

引用类型是通过引用传递的(在未来的某些版本中,再次考虑到引用会再次存储地址,它可能会存储在其他一些数据结构中。)

所以在你的情况下

myInt 是一个 int,它被封装在一个与引用类型无关的类中,因此它将与将存储在“堆”上的类的实例相关联。

我建议,你可以开始阅读 ERIC LIPPERTS 写的博客。

埃里克的博客

于 2010-12-20T07:07:08.497 回答
1

每次在其中创建对象时,它都会进入称为堆的内存区域。像 int 和 double 这样的原始变量如果是局部方法变量,则分配在堆栈中;如果它们是成员变量,则分配在堆中。在方法中,局部变量在调用方法时被压入堆栈,而在方法调用完成时堆栈指针递减。在多线程应用程序中,每个线程都有自己的堆栈,但将共享同一个堆。这就是为什么在你的代码中应该小心避免堆空间中的任何并发访问问题。堆栈是线程安全的(每个线程都有自己的堆栈),但堆不是线程安全的,除非通过代码同步保护。

这个链接也很有用http://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/

于 2015-04-06T18:34:49.140 回答
0

m 是对 MyClass 对象的引用,因此 m 存储在主线程的堆栈中,但 MyClass 的对象存储在堆中。因此 myInt 和 myString 存储在堆中。请注意, m 只是一个引用(内存地址)并且位于主堆栈上。当 m 被释放时,GC 从堆中清除 MyClass 对象有关更多详细信息,请阅读本文的所有四个部分 https://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net-第一部分/

于 2018-03-30T12:50:55.000 回答
-1

根据标准定义(每个人都说),所有值类型都将分配到堆栈中,引用类型将进入堆中。

这是错误的。只有本地(在函数的上下文中)值类型/值类型数组在堆栈上分配。其他所有内容都分配在堆上。

于 2021-06-08T18:41:33.297 回答