C# 这些对象是';是堆栈上的引用还是堆上的引用?

C# 这些对象是';是堆栈上的引用还是堆上的引用?,c#,memory-management,stack,C#,Memory Management,Stack,如果有人能告诉我我是否理解得很好,我将不胜感激: class X { A a1=new A(); // reference on the stack, object value on the heap a1.VarA=5; // on the stack - value type A a2=a1; // reference on the stack, object value on the heap a2.VarA=10; // on the stac

如果有人能告诉我我是否理解得很好,我将不胜感激:

class X
{
   A a1=new A(); // reference on the stack, object value on the heap
   a1.VarA=5;    // on the stack - value type
   A a2=a1;      // reference on the stack, object value on the heap
   a2.VarA=10;   // on the stack - value type         
}
而且
a1
a2
引用都在堆栈上,而它们的“对象”值在堆上。但是
VarA
variable如何,它仍然是纯值类型

class A
{
   int VarA;
}

严格地说,这取决于实施情况。通常,一个.NET开发人员不应该关心这些事情。据我所知,在Microsoft的.NET实现中,值类型的变量存储在堆栈上(当它们在方法中声明时),引用类型对象的数据分配在托管堆上。但是,请记住,当值类型是类的字段时,类数据本身存储在堆上(包括所有值类型字段)。
因此,不要将语义(值类型与引用类型)与分配规则混合使用。这些事情可能有关联,也可能没有关联。

我想你可能有点误解


一般来说,引用类型放在堆上,而我认为(可能是错误的)值类型/局部变量放在堆栈上。但是,您的A1.VarA和A2.VarA示例引用的是引用类型的字段,该字段与堆上的对象一起存储…

在本例中,A1.VarA将位于堆上,因为在执行
a A1=new a()
时,它的空间已分配


如果你只做
inti=5阅读Jeff Richter的文章,以完全理解此主题。

请记住在C中深入阅读:-仅局部变量(在方法中声明的一个)和方法参数位于堆栈中。像上面例子中的varA这样的实例变量位于堆中。

您正在询问有关实现细节的问题,因此答案将取决于特定的实现。让我们考虑一个实际编译的程序版本:

class X 
{ 
    A a1=new A(); // reference on the stack, object value on the heap 
    a1.VarA=5;    // on the Heap- value type (Since it is inside a reference type)
    A a2=a1;      // reference on the stack, object value on the heap 
    a2.VarA=10;   // on the Heap - value type (Since it is inside a reference type)
}
class A { public int VarA; }
class X
{
    static void Main(string[] args)
    {
        A a1 = new A();
        a1.VarA = 5;
        A a2 = a1;
        a2.VarA = 10;
    }
}
下面是在调试模式下运行C#4.0的Microsoft CLR 4.0上发生的情况

此时,堆栈帧指针已复制到寄存器ebp中:

这里我们为新对象分配堆内存

A a1 = new A();
mov         ecx,382518h 
call        FFE6FD30 
返回对eax中堆对象的引用。我们将引用存储在堆栈插槽ebp-48中,这是一个与任何名称都不关联的临时插槽。记住,a1尚未初始化

mov         dword ptr [ebp-48h],eax 
现在我们将刚刚存储在堆栈上的引用复制到ecx中,ecx将用于指向ctor调用的“this”指针

mov         ecx,dword ptr [ebp-48h] 
call        FFE8A518 
现在我们称之为ctor

mov         ecx,dword ptr [ebp-48h] 
call        FFE8A518 
现在,我们再次将存储在临时堆栈插槽中的引用复制到寄存器eax中

mov         eax,dword ptr [ebp-48h] 
现在我们将eax中的引用复制到堆栈插槽ebp-40中,即a1

mov         dword ptr [ebp-40h],eax 
现在我们必须将a1提取到eax中:

a1.VarA = 5;
mov         eax,dword ptr [ebp-40h] 
记住,eax现在是a1引用的对象的堆分配数据的地址。该对象的VarA字段是对象中的四个字节,因此我们将5存储到其中:

mov         dword ptr [eax+4],5 
现在我们将a1的堆栈插槽中的引用复制到eax中,然后将其复制到a2的堆栈插槽中,即ebp-44

A a2 = a1;
mov         eax,dword ptr [ebp-40h] 
mov         dword ptr [ebp-44h],eax 
现在,正如您所期待的,我们再次将a2放入eax,然后将引用四个字节写入VarA,将0x0A写入VarA:

a2.VarA = 10;
mov         eax,dword ptr [ebp-44h] 
mov         dword ptr [eax+4],0Ah

因此,您的问题的答案是,对对象的引用存储在堆栈中的三个位置:ebp-44、ebp-48和ebp-40。它们存储在eax和ecx的寄存器中。对象的内存(包括其字段)存储在托管堆上。这都是在微软CLR v4.0调试版本的x86上实现的。如果您想知道在其他配置中如何在堆栈、堆和寄存器上存储内容,则可能会完全不同。引用可以全部存储在堆上,也可以全部存储在寄存器中;可能根本没有堆栈。这完全取决于jit编译器的作者决定如何实现IL语义。你的问题很重要,我也想到了。所有文档都说,值是堆栈,引用是堆,但正如上面提到的,这只是方法内部的代码。在学习的阶梯上,我意识到所有程序代码都是从属于堆的实例的方法内部开始的。因此,从概念上讲,堆栈与堆在术语上是不相等的,就像所有文档都让人困惑一样。堆栈机制只能在方法中找到…

Yes,但该字段的值为int,因此值类型为,对吗?@Petr,所有字段都包含在堆上的引用类型a中。由于此代码并不编译,因此很难描述运行时如何处理它。所有这些语句都打算放在一个方法体中吗?这些是字段声明还是局部变量声明?请注意,在C#的Microsoft实现中,在lambda或匿名方法的局部变量上闭合的局部变量不存储在堆栈上。迭代器块中的局部变量也是如此。这还取决于C#编译器的作者决定如何实现C#语义。局部变量(
a1
a2
)可以实现为托管类型中的字段,在每个堆栈帧中只留下一个引用。我意识到在你帖子的评论中提到这一点会让人想起祖母和吮吸鸡蛋,但我想我还是要提一下:)@Jon:的确如此。在编译器的IL生成阶段,我们产生的错误很少;其中一个是“太多的局部变量”——我不记得限制是什么,但一个方法中的局部变量或临时变量不能超过32K或64K。(很明显,真正的代码没有这个问题,但机器生成的代码可能有这个问题。)我经常认为,在这种情况下,我们不应该产生错误,而应该开始将它们提升到字段中。但这是一个太模糊的场景,无法证明编写和测试的成本是合理的