您正在询问有关实现细节的问题,因此答案将取决于特定的实现。让我们考虑一个实际编译的程序版本:
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 中,这将用于指向 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,然后将引用的四个字节写入 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 语义。