这些对象是在堆栈上还是在堆上?

时间:2010-04-01 09:10:00

标签: 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 stack - value type         
}

同样,a1a2引用都在堆栈上,而它们的“对象”值在堆上。但是VarA变量,它仍然是纯值类型呢?

class A
{
   int VarA;
}

8 个答案:

答案 0 :(得分:27)

您正在询问有关实施细节的问题,因此答案取决于具体的实施方式。让我们考虑一下实际编译的程序版本:

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;
    }
}

这是微软的CLR 4.0,在调试模式下运行C#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] 

现在我们打电话给ctor。

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,然后将参考四个字节用于将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语义。

答案 1 :(得分:10)

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

答案 2 :(得分:2)

我想你可能会有一点误会......

一般来说,引用类型会在堆上运行,而我认为(可能是错误的)值类型/本地会进入堆栈。但是,您的A1.VarA和A2.VarA示例指的是引用类型的字段 - 它与对象一起存储在堆上...

答案 3 :(得分:2)

在这种情况下,当你执行A a1 = new A()时,a1.VarA会在堆上,因为它会被分配。

如果你只是在一个将进入堆栈的函数中执行int i = 5;,但是当你明确声明a1要在堆上分配时,那么与它相关的所有值类型都将放在堆上

答案 4 :(得分:2)

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)
}

答案 5 :(得分:0)

阅读Jeff Richter的CLR via C#以全面了解此主题。

答案 6 :(得分:0)

记得在深度中读取C#: - 只有局部变量(在方法中声明的变量)和方法参数存在于堆栈中。像上面的varA中的实例变量驻留在堆上。

答案 7 :(得分:0)

我也是C#的新手。你的问题非常重要,我也想到了。所有文档都说,值是堆栈,引用是堆,但正如上面所说的那样,它只是用于方法内部的代码。在学习的阶梯上,我意识到所有程序代码都在属于属于堆的实例的方法内部开始。从概念上讲,堆栈与堆的术语不相同,就像所有文档都让人困惑一样。堆栈机制只能在一种方法中找到......