C#对象引用如何在内存中/运行时(在CLR中)表示?

我很想知道C#对象引用如何在运行时(在.NET CLR中)在内存中表示。 想到的一些问题是:

  1. 对象引用占用多少内存? 在类的范围和方法的范围中定义时它是否不同? 根据此范围(堆栈与堆),它所在的位置是否不同?

  2. 对象引用中维护的实际数据是什么? 它只是一个指向它引用的对象的内存地址还是有更多的内存地址? 这是否根据是否在类或方法的范围内定义而有所不同?

  3. 与上述问题相同,但这次是在讨论对引用的引用时,例如在通过引用将对象引用传递给方法时。 1和2的答案如何变化?

如果您了解C / C ++指针,则最容易理解这个答案。 指针只是某些数据的内存地址。

  1. 对象引用应该是指针的大小,通常在32位CPU上为4个字节,在64位CPU上为8个字节。 无论在何处定义,它都是相同的。 它存在的地方取决于它的定义。 如果它是类的字段,它将驻留在它所属的对象的堆上。 如果它是静态字段,则它位于堆的特殊部分中,不受垃圾回收的影响。 如果它是局部变量,它将存在于堆栈中。

  2. 对象引用只是一个指针,可以将其可视化为包含内存中对象地址的int或long。 无论在何处定义,它都是相同的。

  3. 这是作为指针的指针实现的。 数据是相同的 – 只是一个内存地址。 但是,给定的内存地址没有对象。 相反,还有另一个内存地址,它是对象的原始引用。 这是允许修改参考参数的原因。 通常,当方法完成时,参数会消失。 由于对对象的引用不是参数,因此将保留对此引用的更改。 对引用的引用将消失,但不会引用。 这是传递参考参数的目的。

您应该知道的一件事是,值类型存储在适当的位置(没有内存地址,而是直接存储在内存地址所在的位置 – 请参阅#1)。 将它们传递给方法时,会生成一个副本,并在该方法中使用该副本。 当它们通过引用传递时,传递一个内存地址,它将值类型定位在内存中,允许它被更改。

编辑:正如dlev指出的那样,这些答案并不是硬性规定,因为没有规则说明这是必须的。 .NET可以随意实现这些问题。 这是实现它的最可能方式,因为这是英特尔CPU在内部工作的方式,因此使用任何其他方法可能效率低下。

希望我没有太多困惑你,但随意问你是否需要澄清。

.NET堆和堆栈这是对堆栈和堆如何工作的彻底处理。

C#和许多其他使用堆的OOP语言在一般参考中使用Handles而不是指针用于此上下文中的引用(C#也能够使用指针!)指针类比适用于一些一般概念,但这个概念模型分解为类似的问题这个。 请参阅Eric Lippert关于此主题的优秀postHandles is Not Addresses

说Handle是指针的大小是不合适的。 (虽然它可能巧合地相同)Handles是对象的别名,并不要求它们是对象的正式地址。

在这种情况下,CLR碰巧使用句柄的实际地址:从上面的链接:

… CLR实际上实现了托管对象引用作为垃圾收集器拥有的对象的地址,但这是一个实现细节。

所以是的,32位架构上的句柄可能是4个字节,64字节架构上的8个字节,但这不是“肯定”,并且它不是直接因为指针 。 值得注意的是,根据编译器的实现和使用的地址范围,某些类型的指针的大小可能不同

在所有这些上下文中,您可以通过指针类比来对此进行建模,但重要的是要实现Handles不需要是地址。 如果将来想要CLR可以选择改变它,并且CLR的消费者不应该知道更好。

这个微妙点的最终驱动力:

这是一个C#指针:

int* myVariable; 

这是一个C#句柄:

 object myVariable; 

他们不一样。

你可以做一些像指针上的数学,你不应该使用Handles。 如果您的句柄恰好像指针一样实现,并且您使用它就好像它是一个指针,您在某些方面滥用句柄可能会让您以后遇到麻烦。