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

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

此外,也许你可以certificate为什么不是其他人?

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

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

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

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

嗯,首先,它有时只会。 以下内容不会进入堆栈:

  1. 作为类中字段的值类型
  2. 盒装价值类型
  3. 本地值类型,它们是匿名方法的外部变量
  4. 本地值类型,它是迭代器块的外部变量

其次,当它可能完成时,因为它是有效的。 基本上在CLR内存模型中,与堆上的重新分配相比,堆栈释放相对非常便宜。 对于值类型的本地,您可以确定除了本地之外没有其他人会引用内存,因此您可以使用堆栈而不是堆来逃避。 有关详细信息,请参阅Eric Lippert 。

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

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

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

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

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

所以不要太专注于堆栈,这是一个实现细节。 了解价值和参考类型 。

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

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

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

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

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

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