C#中的栈和堆

4

3
在搜索框中输入“stack heap C#”,您将很快找到所需信息。 - Steve Townsend
2个回答

12
在Microsoft实现的C#编译器和CLR中,当值为临时值、局部变量或形式参数时,值类型存储在堆栈上,既不是匿名方法的闭合外变量也不在迭代器块中。
当然,如果您不需要存储东西在堆栈上,为什么要存储呢?一些值类型的本地变量根本不会进入堆栈;它们在其整个生命周期中都停留在寄存器中。
其他值类型的值存储在堆上——装箱值类型、引用类型上的值类型字段等等。
当然,值类型既不存储在堆栈上,也不存储在寄存器中,也不存储在托管堆上;它们可以使用某些完全不受CLR控制的其他内存管理器存储在非托管内存中。
(当然,请注意,在“the stack”中使用“the”是微妙的误导;在进程中可能有许多堆栈。可能不止一个。)
所有这些都是实现细节,如有更改恕不另行通知。
此外,显然使用堆栈分配声明分配的内容将分配在堆栈上。
有关此主题的更多信息,请参见我的文章:

http://blogs.msdn.com/b/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx

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

你为什么在意呢?运行时会为你管理所有这些细节,这样你就不必担心它。你只是好奇吗,还是这会引出一些更大的问题?


“你为什么在意?”:我不能代表问题提问者回答,但对于我来说,我曾经搜索过这方面的信息(以及关于堆栈大小),因为我担心如果在堆栈上有太多结构体,就会出现堆栈溢出异常。 - blizpasta

5

三个经验法则:

  1. 对象存储在堆上。这包括引用类型和装箱值类型的实例。
  2. 局部变量和参数存储在栈上。对于局部值类型,这意味着值本身存储在栈上。对于局部引用类型,只有引用将在栈上(编辑:Eric Lippert指出了异常情况-值类型局部变量闭合外部变量,迭代器块值类型)。
  3. 字段存储在包含实例所在的位置。例如,类的值类型字段将存储在堆上。作为存储在栈上的本地结构体的引用类型字段的引用部分也将在栈上。

2
规则2是错误的。局部变量和参数并不总是存储在堆栈上。值类型的局部变量,如果闭合了外部变量,则存储在堆上。如果块是迭代器块,则所有值类型的局部变量都存储在堆上。当然,您忽略了一个事实,即即使是“在堆栈上”的本地变量也可能不在堆栈上。它们可能被寄存器化。 - Eric Lippert
@Eric Lippert:当然,你是对的;这就是为什么我急于补充说这些只是经验法则 - Ani
@Eric Lippert:此外,考虑到C#编译器对迭代器块的处理,您不认为将在这些方法中声明的变量视为本地变量稍微有些不准确吗? - Ani
他们为什么不是本地变量?它们是在方法体内声明的变量;根据定义,这是局部变量。您是否有与规范中给出的定义不同的“局部变量”定义? - Eric Lippert
@Eric Lippert:就语言而言,是的。但是本地变量最终不会被翻译成编译器生成的私有状态机类中的字段吗?这个“实现细节”对于理解它们为什么最终会出现在堆上非常重要,不是吗? - Ani
是的,局部变量被转换为字段。这不是必须的。例如,迭代器可以实现为纤程,在这种情况下,局部变量将在纤程的堆栈上。在Windows中,纤程对于迭代器来说太昂贵了,所以我们不使用它们。但这是出于性能原因做出的实现决策。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接