您正在询问关于“实现细节”的问题,因此答案将取决于具体的实现。让我们考虑一个实际编译的程序版本:
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;
}
}
在 Microsoft 的 CLR 4.0 上,以 C# 4.0 运行 Debug 模式时会发生以下情况。
此时堆栈帧指针已经被复制到寄存器 ebp 中:
这里我们为新对象分配堆内存。
A a1 = new A();
mov ecx,382518h
call FFE6FD30
该代码返回eax中一个堆对象的引用。我们将引用存储在ebp-48的堆栈槽中,它是一个临时槽,没有与任何名称相关联。请记住,a1尚未初始化。
mov dword ptr [ebp-48h],eax
现在我们将刚刚存储在堆栈中的引用复制到ecx寄存器中,这将被用作调用构造函数的"this"指针。
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中,然后将参考值转换为四个字节,并将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寄存器中。对象的内存,包括其字段,存储在托管堆上。这全部是基于Microsoft CLR v4.0的x86调试版本。如果你想知道如何在其他配置中将东西存储在栈、堆和寄存器中,可能完全不同。引用可以全部存储在堆中,或全部存储在寄存器中;可能根本没有栈。这完全取决于jit编译器的作者决定如何实现IL语义。