当类存储在堆(.NET)上时,为什么结构存储在堆栈中?

时间:2009-05-02 18:37:19

标签: c# .net

我知道类和结构之间的区别之一是结构实例存储在堆栈上,而类实例(对象)存储在堆上。

由于类和结构非常相似。有人知道这种特殊区别的区别吗?

11 个答案:

答案 0 :(得分:39)

(编辑以涵盖评论中的要点)

要强调:值类型和引用类型之间存在差异和相似之处,但这些差异与堆栈与堆的有关,并且与复制语义和引用相关的一切都与语义。特别是,如果我们这样做:

Foo first = new Foo { Bar = 123 };
Foo second = first;

然后是“第一”和“第二”谈论Foo的同一副本?还是不同的副本?恰好,堆栈是一种方便有效的处理值类型作为变量的方式。但这是一个实施细节。

(结束编辑)

重新整理“价值类型进入堆栈”的事情...... - 价值类型总是进入堆栈;

  • 如果他们是课堂上的字段
  • 如果装箱
  • 如果它们是“捕获的变量”
  • 如果它们在迭代器块中

然后他们进入堆(最后两个实际上只是第一个的异乎寻常的例子)

class Foo {
    int i; // on the heap
}

static void Foo() {
    int i = 0; // on the heap due to capture
    // ...
    Action act = delegate {Console.WriteLine(i);};
}

static IEnumerable<int> Foo() {
    int i = 0; // on the heap to do iterator block
    //
    yield return i;
}

此外,Eric Lippert(如前所述)在此主题上有excellent blog entry

答案 1 :(得分:10)

在实践中,出于某些目的能够在堆栈上分配内存是有用的,因为这些分配非常快。

然而,值得注意的是,没有基本保证将所有结构放置在堆栈上。 Eric Lippert最近就此主题撰写了an interesting blog entry

答案 2 :(得分:9)

这是一个很好的问题;我没有在Marc Gravell链接的文章中介绍它。这是第二部分:

http://blogs.msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx

答案 3 :(得分:7)

每个进程都有一个数据块,由两个不同的可分配内存段组成。这些是堆栈和堆。 Stack主要用作程序流管理器并保存局部变量,参数和返回指针(在从当前工作函数返回的情况下)。

与结构类(或基本类型 - 整数,字符等)之类的值类型相比,类非常复杂并且大多数都是非常大的类型。因为堆栈分配应该专注于程序流的效率,它不能提供最优的服务。保持大型物体的环境。

因此,为了满足这两个期望,这个独立的架构出现了。

答案 4 :(得分:1)

在某些语言中,如C ++,对象也是值类型。

要找到相反的示例更难,但在经典的Pascal联合结构下只能在堆上实例化。 (正常的结构可能是静态的)

简而言之:这种情况是一种选择,而不是一项严格的法律。由于C#(和之前的Java)缺乏程序基础,人们可以问自己为什么它需要结构。

它存在的原因可能是需要它用于外部接口并具有高性能和紧密复杂(容器)类型的组合。一个比课速更快的人。然后最好将其设为值类型。

答案 5 :(得分:1)

编译器和运行时环境如何处理内存管理已经成长了很长一段时间。堆栈内存v.s.堆内存分配决策与编译时可以知道的内容有很多关系,而且在运行时可以知道什么。这是在托管运行时之前。

通常,编译器可以非常好地控制堆栈上的内容,它可以根据调用约定来确定清理的内容。另一方面,堆更像是狂野的西部。编译器无法很好地控制事情的来去时间。通过在堆栈上放置函数参数,编译器可以创建范围 - 可以在调用的生命周期内控制范围。这是放置值类型的自然场所,因为它们易于控制,而不是可以将内存位置(指针)分发给他们想要的任何人的引用类型。

现代内存管理改变了很多这方面的内容。 .NET运行时可以通过复杂的垃圾收集和内存管理算法来控制引用类型和托管堆。 This is also a very, very deep subject

我建议你查看编译器的一些文本 - 我在Aho长大,所以我recommend that。您还可以通过阅读Gosling了解该主题。

答案 6 :(得分:1)

Marc Gravell已经很好地解释了价值和参考类型如何被复制的区别,这是它们之间的主要区别。

至于为什么通常在堆栈上创建值类型,这是因为它们的复制方式允许它。堆栈在性能方面比堆有一些明显的优势,特别是因为编译器可以计算在某个代码块中创建的变量的确切位置,这样可以更快地访问。

创建引用类型时,您会收到对堆中存在的实际对象的引用。每当您与对象本身进行交互时,都会有一个小的间接层。无法在堆栈上创建这些引用类型,因为堆栈中值的生命周期在很大程度上取决于代码的结构。例如,当函数返回时,方法调用的函数框将从堆栈中弹出。

但是,对于值类型,它们的复制语义允许编译器根据创建的位置将其放入堆栈中。如果你创建一个局部变量来保存方法中的结构实例然后返回它,那么将创建一个它的副本,如上面Marc所述。这意味着可以将值安全地放入堆栈中,因为实际实例的生命周期与方法的功能框相关联。无论何时将它发送到当前函数之外的某个地方,都会创建它的副本,因此如果将原始实例的存在与函数的范围联系起来并不重要。沿着这些方向,您还可以看到为什么闭包捕获的值类型需要进入堆中:它们的范围比它们的范围更长,因为它们也必须可以从闭包内访问,它可以自由传递。

如果它是引用类型,那么你不会返回对象的副本,而是返回引用,这意味着实际值必须存储在其他地方,否则,如果返回引用和对象的生命周期它与创建它的范围有关,最终会指向内存中的空白区域。

区别不在于“值类型在堆栈上,堆上的引用类型”。真正的一点是,访问堆栈中的对象通常更有效,因此编译器会尝试将那些值放在那里。事实证明,由于它们的复制语义,值类型比引用类型更适合该法案。

答案 7 :(得分:0)

我相信是否使用堆栈或堆空间是两者之间的主要区别,或许这篇文章将对您的问题有所了解:Csharp classes vs structs

答案 8 :(得分:0)

主要区别在于堆可能包含永久存在的对象而堆栈中的某些东西是临时的,因为当退出封闭的调用点时它将消失。这是因为当一个人进入一个方法时,它会增长以保存局部变量以及调用者方法。当方法正常退出(ab)时,例如返回或由于异常,每个帧必须从堆栈中弹出。最终会弹出感兴趣的框架,其中的所有内容都会丢失。

答案 9 :(得分:0)

关于使用堆栈的重点是它自动实现并尊重范围。存储在堆栈上的变量一直存在,直到创建它的函数退出并且弹出函数堆栈帧。具有本地范围的东西对于堆栈存储是很自然的,具有更大范围的东西在堆栈上更难管理。堆上的对象可以具有以更复杂的方式控制的生命周期。

编译器总是将堆栈用于变量 - 值或引用它几乎没有区别。引用变量不必将其值存储在堆栈中 - 它可以在任何地方,如果引用的对象很大并且有多个引用,则堆会提高效率。关键是引用变量的范围与它引用的对象的生命周期不同,即一个变量可能会被弹出堆栈而被破坏,但它引用的对象(在堆上)可能仍然存在。

如果值类型足够小,您可以将它存储在堆栈上,而不是在堆上引用它 - 它的生命周期与变量的范围相关联。如果值类型是较大引用类型的一部分,那么它也可能有多个引用,因此将它存储在堆上并将其生命周期与任何单个引用变量分离是更自然的。

堆栈和堆是关于生命周期的,值v引用语义几乎是副产品。

查看Value and Reference

答案 10 :(得分:-2)

值类型在堆栈上,引用类型在堆上。结构是一种值类型。

虽然在规范中没有关于此的担保,因此在将来的版本中可能会有所改变:)