为什么值类型存储在Stacks上?

时间:2009-12-19 06:18:40

标签: c# clr language-design

为什么C#(。Net)更喜欢堆栈来存储值类型?这种设计背后的主要原因是什么?是否因为对堆栈的读/写操作更好地利用了机器处理器?

另外,也许你可以证明为什么不为他人辩护?

6 个答案:

答案 0 :(得分:29)

Eric Lippert讨论了这个here;首先,“值类型存储在堆栈中”是不正确的。他们有时是,但不是:

  • 类上的字段
  • 捕获的变量
  • 迭代器块中的变量

可以存储在堆栈中时,它是一种模拟其生命周期的便捷方式,但不是 required 将它们存储在堆栈中。例如,您可以编写一个不拥有堆栈的编译器+ CLI。

答案 1 :(得分:13)

C#不会在堆栈中存储任何内容。 C#是一种编程语言。因此,更正确的问题版本是为什么Microsoft C#编译器发出CIL指令以在堆栈上分配值类型?

嗯,首先,它有时只会这样。以下内容不会出现在堆栈中:

  1. 作为类中字段的值类型
  2. 盒装价值类型
  3. 作为匿名方法的外部变量的本地值类型
  4. 作为迭代器块外部变量的本地值类型
  5. 其次,当它可能完成时,因为它是有效的。基本上在CLR内存模型中,与堆上的解除分配相比,堆栈上的释放相对非常便宜。对于值类型的本地,您可以确定除了本地之外没有其他人会引用内存,因此您可以使用堆栈而不是堆来逃避。有关详细信息,请参阅Eric Lippert

    最后,值类型特殊的是它们具有值类型语义(按值复制),而不是它们有时在堆栈上分配。 C#规范中没有要求编译器发出指令以在堆栈上分配值类型。 C#规范要求的是值类型具有值类型语义。

答案 2 :(得分:4)

正如@Akash指出的那样,它主要与内存有关。在CLR的设计过程中,我注意到(我的猜测是来自Java的经验)表示小的原始类型作为具有处理垃圾收集器的句柄的对象导致了大量的跟踪开销。所以设计师想要一个不需要跟踪的“轻量级”物体。

CLI规范中没有特定要求对基元进行堆栈分配;它是机器上实现的工件。关键是运行时知道实例的位置是由于构造定义良好的内存模式(称为帧)而不是GC的已分配对象索引。在x86(和类似的)机器上,这可以使用堆栈有效地完成。

答案 3 :(得分:2)

你的陈述并非完全正确。更好的版本:C#将局部变量存储在堆栈中 这不是特殊的或新的,(几乎)所有编程语言都使用堆栈作为局部变量和方法返回地址。硬件支持这一点。

此外,Valuetypes可以是局部变量或引用类型中的字段。因此,值类型并不总是存储在堆栈中。更有用的声明:referencetypes 从不存储在堆栈中。

所以不要太专注于堆栈,这是一个实现细节。了解value- and reference-types

答案 4 :(得分:0)

堆栈操作或堆操作,它们都与访问位于两个不同位置的内存地址相同。

值类型是小型,整数,字节等,它们的大小很小,并且在数学计算方面非常频繁地引用它们。由于它们的大小非常小,最大为4到16个字节(为了获得最佳性能,你不应该在值类型中使用超过16个字节),在堆上释放这么小的空间并解除分配,垃圾收集等会非常昂贵。

我们输入的每个方法,平均而言我们定义10个本地值类型以在内部使用它,这在堆上作为引用类型将非常昂贵。

堆栈可以轻松地增长和收缩(不是堆栈大小,但是用于当前方法的堆栈部分!!),因为valuetypes只是作为堆栈指针的偏移量来处理,并且它们的分配和释放很容易,因为它的简单增量和使用所有值类型的总大小对stackpointer进行破坏。

在引用类型的其他地方,每个引用对象都有自己的分配和大小调整,而且CLR必须维护对象表,这类似于内存中实际指针的索引,以避免缓冲区溢出。因此,您使用的一个对象(引用类型)实际上有两个存储,CLR的引用表中有一个索引条目,以及实际的内存空间。这就是为什么它容易和快速地在堆栈上存储值类型。

答案 5 :(得分:0)

对于正确的程序操作,重要的是值类型和类类型实体比对它们的任何引用都要长。创建类类型对象时,会创建一个可以自由复制到任何范围的引用。因此,即使在当前范围退出之后,对该对象的引用完全有可能继续存在。相反,当创建一个value-type变量时,唯一可以创建的引用是一个短期类型,它将在当前作用域退出之前消失。当现有范围退出时,值类型变量不能存在引用这一事实使得将这些变量存储在堆栈中是安全的。