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

时间:2012-02-29 00:20:59

标签: c# memory clr object-reference

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

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

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

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

2 个答案:

答案 0 :(得分:14)

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

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

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

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

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

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

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

答案 1 :(得分:14)

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

C#和许多其他使用堆的OOP语言在一般的参考语言中使用 Handles not Pointers 来获取此上下文中的引用(C#也能够使用指针!)指针类比适用于一些一般概念但是这个概念模型会因为这样的问题而崩溃。请参阅Eric Lippert关于此主题Handles are Not Addresses

的优秀文章

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

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

  

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

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

对于所有这些上下文,您可以通过指针类比来对此进行建模,但重要的是要实现Handles不需要是地址。 CLR可以选择在未来改变它,CLR的消费者不应该更好地了解。

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

这是一个C#指针:

int* myVariable;

这是一个C#句柄:

object myVariable;

他们不一样。

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