结构体总是分配堆栈还是有时分配堆?

问题描述 投票:0回答:7

我的印象是,在 C# 中,结构元素是在堆栈上分配的,因此从创建它们的方法返回时会消失。但是如果我将结构值放入列表中并返回它会发生什么?元素得以幸存。 结构实例有时会分配在堆上吗?

internal struct Stru
{
  public int i;
}

internal class StruTry
{
  public List<Stru> Get(int max)
  {
    var l = new List<Stru>();
    for (int i = 0; i < max; i++)
      l.Add(new Stru {i=i});

    return l;
  }
}

代码打印0,1,2

[Test]
public void T()
{
  var ll = new StruTry().Get(3);
  foreach (var stru in ll)
    Console.WriteLine("* "+ stru.i);
}
c# struct allocation
7个回答
81
投票

首先,阅读 Eric Lippert 的这篇文章:堆栈是一个实现细节。接下来是关于值类型的真相。 至于你的具体问题

结构实例有时会分配在堆上吗?

是的,它们有时会分配在堆上。有很多关于何时可以在堆上分配它们的示例。如果它们被装箱,或者它们是类中的字段,或者它们是数组的元素,或者它们是已封闭的值类型变量的值,等等。

但是如果我将结构值放入列表中并返回它会发生什么?元素得以幸存。

您正在以正确的方式思考这一点,这是可以分配值类型的要点之一。有关更多详细信息,请参阅我在关于值类型的真相中提到的第二篇文章。但请记住堆栈是一个实现细节。关键的一点是你真的不需要关心这些东西。您应该关心值类型和引用类型之间的语义差异。


30
投票

结构就像

int
。如果你有一个本地
int
,它通常会在堆栈上,如果你有一个
int
列表,它们会直接存储在列表的内部数组中,该数组位于堆上。结构体的行为方式相同。


7
投票

但是如果我将结构值放入列表中并返回它会发生什么?元素得以幸存。

从技术上讲,添加到“列表”中的值不是相同的值,它们是基于值的副本。例如,如果您修改原始文件,这些更改将不会传递到列表中的副本中。此外,“List”返回指定索引处的值的副本。这意味着如果结构是可变的并且您修改从“List”返回的值,则

List<t>
中的值将保持不变。数组的情况并非如此,因为数组索引提供对实际变量的访问。


2
投票
有时可以在堆上分配

所有类型。除此之外,堆/堆栈是 CLR 的实现细节,而不是 C# 规范中的实现细节,因此您不应该依赖这些东西。请参阅此处了解有关此主题的优秀博客文章。


1
投票

据我记忆...

值类型的位置取决于它们的声明位置。方法变量被分配、存储在堆栈中,并在堆栈帧中执行方法后被删除。声明为引用类型一部分的值类型存储在封闭类型结构内的堆上。

如果我错了请告诉我!


1
投票

结构类型的存储位置(变量、字段、参数、数组槽等)保存结构的公共和私有字段。如果该存储位置位于堆栈上,则该结构的字段也将位于堆栈上。如果它位于另一个类或结构中,则该结构的字段将被存储作为该其他类或结构实例的一部分

类类型的存储位置保存对完整类对象的引用,该对象始终要么(1)存储在与保存引用的存储位置完全独立的位置,要么(2)该存储位置的类对象是一个字段。


0
投票

使用 C# 7.2,您可以声明始终在堆栈上分配且不会放入堆中的结构 - 使用

ref
关键字:

public ref struct MyStackStruct
{
..

但是它有很多限制。

所有详细信息均在官方文档中:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/ref-struct

© www.soinside.com 2019 - 2024. All rights reserved.