【问题标题】:.NET Value Types and Reference Types.NET 值类型和引用类型
【发布时间】:2012-07-20 15:47:09
【问题描述】:

“值类型是堆栈分配的,而引用类型存在于托管堆上。”

如果我在类的方法中有一个局部变量(如 int a=2;),它分配在哪里?

在我们的示例中,值类型包含在引用类型中。由于引用存在于托管堆中,我假设这里的值类型(int a)也在托管堆中而不是堆栈中。

我错过了什么吗?

【问题讨论】:

  • 值类型嵌入到包含存储中。这可以是堆栈,但也可以是其他存储。
  • @Oded 那个问题具体是“类的字段”;这个问题是“类方法中的局部方法变量”;很不一样
  • @MarcGravell - 是的。我真的想参考问题的第一行 - 那句话非常具有误导性。
  • 嗯,是的,现在和你在一起。我讨厌那句话。我一直希望它与“结构适用于不需要方法的情况”相邻。

标签: .net


【解决方案1】:

主要是 JIT 编译器决定局部变量的存储位置。这是一个繁重的架构实现细节,让我们将其限制为 x86 抖动。它做出的常见选择:

  • 无处可去。在您给出的非常简单的示例中会发生这种情况,抖动优化器可以看到局部变量已初始化但未在任何地方使用并将消除它。

  • 在 CPU 寄存器中。这是一个非常重要的优化,没有存储位置更快。至少在方法体执行的部分时间里,变量几乎总是存在于寄存器中,这是必要的,因为许多 cpu 指令要求操作数首先存在于寄存器中。抖动只会在变量用完寄存器(x86没有很多)并且需要将寄存器重新用于另一个操作时才会将变量溢出到堆栈帧

  • 在方法的堆栈帧中。每个人都认为的传统方式。变量存储在 EBP 寄存器的固定偏移处。访问这些变量非常快,不如将它们存储在寄存器中时快。

然而,我还必须谈谈编译器影响在语言中具有本地范围的变量的存储位置的方式(感谢 Marc):

  • 在垃圾收集堆上。这是由编译器在重写代码以实现迭代器、捕获匿名方法或 lambda 表达式的变量或实现用 async 关键字标记的方法时完成的。局部变量成为隐藏类的字段,并像往常一样在堆上分配。

  • 在加载程序堆中。对 C# 程序员来说很奇怪,但由带有 Static 关键字的 VB.NET 编译器支持。由编译器实现的功能,它的作用类似于 C# 静态字段,但范围仅限于方法主体。使用大量自动生成的代码来确保正确初始化,即使是从线程调用时也是如此。

这几乎涵盖了变量的所有可能存储位置 :) 尽管我在提出 [ThreadStatic] 示例时遇到了麻烦。这可能是信息过载的一种情况,最常见的方法集中在第 2 条和第 3 条。当然还有第 3 点,可以有效地思考托管代码的工作方式。

【讨论】:

  • 一些非常有效的观点;我在我的帖子中添加了一个说明,它只解决了 IL 中发生的情况,即 pre-JIT。
【解决方案2】:

首先,应该注意的是,您帖子的第一行具有误导性、不完整和不准确。值类型几乎可以在任何地方。

在我们的示例中,值类型包含在引用类型中。

这里的“包含在其中”具有误导性。您将其与此混为一谈的“包含在其中”是“实例字段”。这不适用于方法局部变量。方法局部变量,作为实现细节,存在于堆栈中......除非它们不存在!其中包括迭代器块和捕获的变量。既然你没有提到这两件事,答案可能是“在堆栈上”。

我还应该注意,即使对于 引用类型的方法局部变量,变量(即引用,而不是对象)仍然存在于堆栈中(除了如果没有,则完全一样的规则)。

请注意,我将讨论限制在在 IL 术语中,即 C# 编译器的作用。 Hans 说 JIT 可以在看到 IL 时为所欲为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-10
    • 1970-01-01
    • 2014-09-21
    • 2015-04-29
    • 2015-07-18
    相关资源
    最近更新 更多