“值类型是堆栈分配的,而引用类型存在于托管堆上。”
如果我在类的方法中有一个局部变量(如 int a=2;),它分配在哪里?
在我们的示例中,值类型包含在引用类型中。由于引用存在于托管堆中,我假设这里的值类型(int a)也在托管堆中而不是堆栈中。
我在这里错过了什么吗?
“值类型是堆栈分配的,而引用类型存在于托管堆上。”
如果我在类的方法中有一个局部变量(如 int a=2;),它分配在哪里?
在我们的示例中,值类型包含在引用类型中。由于引用存在于托管堆中,我假设这里的值类型(int a)也在托管堆中而不是堆栈中。
我在这里错过了什么吗?
主要是 JIT 编译器确定局部变量的存储位置。这是一个繁重的架构实现细节,让我们将其限制为 x86 抖动。它做出的常见选择:
无处。在您给出的非常简单的示例中会发生这种情况,抖动优化器可以看到局部变量已初始化但未在任何地方使用并将消除它。
在 CPU 寄存器中。这是一个非常重要的优化,没有存储位置更快。至少在方法体执行的部分时间里,变量几乎总是存在于寄存器中,这是必要的,因为许多 cpu 指令要求操作数首先存在于寄存器中。抖动只会在变量用完寄存器(x86没有很多)并且需要重新使用寄存器进行另一个操作时才会将变量溢出到堆栈帧
在方法的堆栈帧中。每个人都认为的传统方式。变量存储在 EBP 寄存器的固定偏移处。访问这些变量非常快,不如将它们存储在寄存器中时那么快。
然而,我还必须讨论编译器影响在语言中具有本地范围的变量的存储位置的方式(感谢 Marc):
在垃圾收集堆上。这是由编译器在重写代码以实现迭代器、捕获匿名方法或 lambda 表达式的变量或实现用async关键字标记的方法时完成的。局部变量成为隐藏类的字段,并正常分配在堆上。
在加载程序堆中。对 C# 程序员来说很奇怪,但由带有 Static 关键字的 VB.NET 编译器支持。由编译器实现的功能,它的作用类似于 C# 静态字段,但范围仅限于方法主体。使用大量自动生成的代码来确保它被正确初始化,即使是从线程调用时也是如此。
它涵盖了变量的几乎所有可能的存储位置:) 尽管我在提出 [ThreadStatic] 示例时遇到了麻烦。这可能是信息过载的一种情况,最常见的方法集中在第 2 条和第 3 条。当然还有第 3 条有效地思考托管代码的工作方式。
首先,应该注意的是,您帖子的第一行具有误导性、不完整和不准确。值类型几乎可以在任何地方。
在我们的示例中,值类型包含在引用类型中。
这里的“包含在其中”具有误导性。您将其与此混为一谈的“包含在其中”是“实例字段”。这不适用于方法局部变量。方法局部变量,作为实现细节,存在于堆栈中......除非它们不存在!其中包括迭代器块和捕获的变量。由于您没有提到这些事情,答案可能是“在堆栈上”。
我还应该注意,即使对于引用类型的方法局部变量,变量(即引用,而不是对象)仍然存在于堆栈中(除非它不存在,完全相同的规则)。
请注意,我将讨论限制在 IL 术语中,即C# 编译器的作用。Hans 说 JIT 可以在看到 IL 时为所欲为,这是非常正确的。