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

Posted

技术标签:

【中文标题】结构总是堆栈分配还是有时堆分配?【英文标题】:Are Structs always stack allocated or sometimes heap allocated? 【发布时间】:2019-10-25 16:21:54 【问题描述】:

我的印象是,在 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);

【问题讨论】:

【参考方案1】:

首先,阅读 Eric Lippert 在The Stack is an Implementation Detail 上的这篇文章。关注The Truth about Value Types。 至于你的具体问题

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

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

但是如果我将结构值放在一个列表中并返回它会发生什么?元素幸存下来。

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

【讨论】:

最后一点是只关心“我是按值传递还是按引用传递”而不是“我的对象是在堆栈还是堆上”——这对我来说很困难,因为我主要用 C++ 编程.我认为任何 C/C++ 程序员都会考虑对象所在的位置。在 C/C++ 中,结构(和 C++ 中的类)可以存在于任一位置,具体取决于它们的声明方式。似乎任何 C/C++ 程序员在 C# 中工作的主要事情就是打破这种习惯,永远不要考虑对象的位置。 @jason 像 struct sockaddr 这样的通用结构怎么样,如何在不知道其正确大小的情况下进行堆栈分配(因为根据定义,这个通用结构比 IPv6 结构 sockaddr_in6 小) @jason 这是什么意思“如果它们是已经关闭的值类型变量的值”? @Pingpong 检查closure【参考方案2】:

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

【讨论】:

甜蜜。我在要定义的结构中有一个数组,并且非常担心该结构中的值会占用堆栈。很高兴听到数组不会吞噬结构。堆有(看似)无限的空间!!!【参考方案3】:

所有类型有时可以在堆上分配。除此之外,堆/堆栈是 CLR 的实现细节,而不是 C# 规范,所以你不应该依赖这些东西。请参阅 here 了解有关此主题的优秀博文。

【讨论】:

【参考方案4】:

据我所知……

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

如果我错了,请告诉我!

【讨论】:

如果“方法变量”是指局部变量,那么您的说法并不完全准确。例如,如果局部变量在 lambda 表达式中被封闭,它将被提升为编译定义类的成员,并像类的其他字段一样在堆上分配。关键是这样的局部变量可能比定义方法的寿命更长,因此需要从堆栈中分配出去。【参考方案5】:

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

从技术上讲,添加到“列表”的值不是相同的值,它们是基于值的副本。例如,如果您修改了原始文件,这些更改将不会被带到列表中的副本。此外,“列表”返回指定索引处的值的副本。这意味着如果结构是可变的并且您修改了从“列表”返回的值,那么List&lt;t&gt; 中的值将保持不变。数组不是这种情况,因为数组索引提供了对实际变量的访问。

【讨论】:

【参考方案6】:

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

类类型的存储位置持有一个对完整类对象的引用,该对象始终要么(1)存储在与持有引用的存储位置完全分开的某个地方,要么(2)该存储位置是一个字段的类对象。

【讨论】:

以上是关于结构总是堆栈分配还是有时堆分配?的主要内容,如果未能解决你的问题,请参考以下文章

是应该在堆栈还是堆上分配pthread函数参数?

c#结构/类堆栈/堆控制?

如何判断某个东西是堆分配的还是堆栈分配的?

函数内的指针变量指向堆栈还是堆?

数据结构之堆栈

缓冲区溢出及堆栈/堆操纵