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

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

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

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

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

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

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

(结束编辑)

重新整个“值类型进入堆栈”的事情…… – 值类型并不总是在堆栈上;

  • 如果他们是一个class级的领域
  • 如果他们是盒装的
  • 如果他们是“捕获变量”
  • 如果它们在迭代器块中

然后他们进入堆(最后两个实际上只是第一个的异国情况)

 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 Foo() { int i = 0; // on the heap to do iterator block // yield return i; } 

此外,Eric Lippert(如前所述)在此主题上有一篇很棒的博客文章

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

但是,值得注意的是,没有任何基本保证将所有结构放置在堆栈上。 Eric Lippert最近写了一篇关于这个主题的有趣博客文章 。

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

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

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

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

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

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

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

现代内存管理改变了很多这一点。 .NET运行时可以通过复杂的垃圾收集和内存管理算法来控制引用类型和托管堆。 这也是一个非常非常深刻的主题 。

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

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

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

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

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

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

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

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

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

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

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

我相信是否使用堆栈或堆空间是两者之间的主要区别,也许本文将对您的问题有所了解: Csharp类与结构

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

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

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

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

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

看看价值和参考

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

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