引用类型存在于堆中,值类型存在于栈中
Posted
技术标签:
【中文标题】引用类型存在于堆中,值类型存在于栈中【英文标题】:Reference types live on the heap, value types live on the stack 【发布时间】:2011-04-02 07:23:51 【问题描述】:在阅读“深入了解 C#”时,我正在阅读标题为“引用类型存在于堆上,值类型存在于堆栈上”的部分。
现在我能理解的是(主要是 ref 类型):
class Program
int a = 5; // stored in heap
public void Add(int x, int y) // x,y stored in stack
int c = x + y; // c stored in stack
只是想澄清一下我的假设是否正确。谢谢。
编辑: 我应该使用差异变量,因为我认为我最初造成的混乱。所以我修改了代码。
编辑: 是的,正如 Jon 所提到的 - 这是一个神话。我应该提到这一点。我很抱歉。
【问题讨论】:
【参考方案1】:https://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-stack-is-an-implementation-detail-part-one
整个“堆上的引用类型,堆栈上的值类型”不仅是一种不好的看待方式,而且也是错误的。
【讨论】:
你可以修改代码并解释一下-我的意思是一个存储在堆中的值类型的例子 它们适用于大多数现有实现。没什么可说的,没有人不能构建无堆栈 CLR。 x 和 y 不会在堆栈中吗?没有什么可说的,优化不允许将引用类型放在堆栈上并在堆栈展开时清理它。今天没有这样做,但可能会这样做。了解堆栈和堆做了什么是很好的,但只有在选择真正合适的值与引用类型之后。一方面,人们谈论堆栈的效率往往会低估 CLR 堆的效率。 @siride:我应该指出,该部分专门将此称为神话:) @Jon:这不仅是一个 CLR 实现问题,也是一个 C# 编译器实现问题。 C# 编译器没有说明事物的存储方式。编译器可以在不更改 CLR 的情况下进行更改,例如使用一个类来存储每个方法的局部变量......并且语言规范根本不必更改。 @siride:我的意思是我有一个我明确说是错误的神话列表,并且“引用类型存在于堆上,值类型存在于堆栈上”就是这些神话之一.这里的问题听起来像是这本书在断言它,而实际上它在反驳它:)【参考方案2】:c
留在堆栈上,因为至少是一个值类型,而 a
因为是引用类型的字段而在托管堆中
【讨论】:
请注意,c
的值将在堆栈上(在当前实现中),即使它的类型(例如)StringBuilder
。只是变量的值是对对象的引用——它是堆上的 object。我发现,一旦你区分了一个变量、它的值以及该值实际代表的内容(例如,一个引用而不是一个实际的对象),很多事情就会变得更加清晰。
@Jon:谢谢你的留言!【参考方案3】:
对于幕后发生的事情,我可能是一个有点有用的抽象概念。但在任何当前发布的 JIT 编译器版本中都不是这样。这可能是问题的症结所在,实际分配位置是 JIT 编译器的实现细节。
至少有六个地方的值类型值可以适应主流(x86 和 x64)抖动:
在堆栈帧中,通过局部变量声明或方法调用放置 在 CPU 寄存器中,由 JIT 在发布版本中执行的非常常见的优化。并且用来传递参数给一个方法,前两个为x86,四个为x64。尽可能使用局部变量 在 FPU 堆栈上,由 x86 抖动用于浮点值 在 GC 堆上,当值是引用类型的一部分时 在 AppDomain 的加载器堆上,当变量声明为静态时 当变量具有 [ThreadStatic] 属性时,在线程本地存储中。引用类型对象通常分配在 GC 堆上。但是我知道一个特定的例外,从源代码中的文字产生的实习字符串被分配在 AppDomain 的加载程序堆中。这在运行时完全像一个对象,只是它没有链接到 GC 堆,收集器根本看不到它。
解决您的代码 sn-p:
是的,“a”很可能存储在 GG 堆中 “x”总是在 x86 和 x64 上的 CPU 寄存器中传递。 “y”将在 x64 的 CPU 寄存器中,在 x86 的堆栈中。 “c”可能根本不存在,被JIT编译器删除,因为代码没有效果。【讨论】:
为什么第一个参数x
将在堆栈上,第二个参数 y
- 并非总是如此?附言c
将在发布模式下被删除
两个 CPU 寄存器用于 x86 内核,四个用于 x64 内核。 “this”指针需要一个。【参考方案4】:
引用类型的存储位置(变量、字段、数组元素等)保存对堆上对象的引用;原始值类型的存储位置在其内部保存它们的值;结构类型的存储位置包含它们的所有字段,每个字段都可以是引用或值类型,在它们自己内部。如果一个类实例包含两个不同的非空字符串,一个 Point 和一个整数,则该点的 X 和 Y 坐标以及独立整数和对这两个字符串的引用都将保存在一个堆中目的。每个字符串都将保存在一个 不同的 堆对象中。关于类与结构的存储位置的关键点是,除了类实体持有对自身的引用之外,类或结构中的每个非空引用类型字段都将持有对某些 other 对象,它将在堆上。
【讨论】:
【参考方案5】:用 C/C++ 术语来考虑它。
任何时候你做一个“新”的东西,或者使用malloc,在堆上——也就是说,“对象”在堆上,指针本身被放置在结构范围内的堆栈上(或函数,它实际上只是另一个结构),它是它的一部分。如果是局部变量或引用类型(指针),则入栈。
换句话说,引用类型指向的>object
【讨论】:
【参考方案6】:从他的famous blog 中引用 Jon Skeet 关于引用和值类型在 .Net 应用程序中的存储方式和位置:
变量的内存槽存储在堆栈或 堆。这取决于声明它的上下文:
每个局部变量(即在方法中声明的变量)都存储在堆栈中。这包括引用类型变量 - 变量本身是 在堆栈上,但请记住引用类型变量的值 只是一个引用(或 null),而不是对象本身。方法 参数也算作局部变量,但如果它们被声明为 ref 修饰符,他们没有自己的插槽,但与 调用代码中使用的变量。请参阅我关于参数的文章 通过了解更多详情。 引用类型的实例变量总是在堆上。这就是对象本身“存在”的地方。 值类型的实例变量存储在与声明值类型的变量相同的上下文中。内存插槽用于 instance 有效地包含了 实例。这意味着(鉴于前两点)一个结构 在方法中声明的变量将始终在堆栈上,而 作为类的实例字段的结构变量将位于 堆。 每个静态变量都存储在堆中,无论它是在引用类型还是值类型中声明的。只有 无论创建多少实例,一共 1 个插槽。 (那里 不需要为该插槽创建任何实例 不过。)关于变量存在于哪个堆的详细信息是 复杂,但在 MSDN 文章中详细解释 主题。
【讨论】:
以上是关于引用类型存在于堆中,值类型存在于栈中的主要内容,如果未能解决你的问题,请参考以下文章