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

13

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

  1. 一个对象引用占用多少内存?它在类范围和方法范围下有所不同吗?它所在的位置是栈还是堆,取决于这个范围吗?

  2. 对象引用实际维护什么数据?它只是指向所引用对象的内存地址,还是还有其他内容?定义在类或方法的作用域中是否有所不同?

  3. 当引用传递到方法中时,与上述相同的问题,即关于对引用的引用,这时候答案1和2会发生什么变化?


3
请注意,这些问题都是关于实现细节的(可能会发生变化),而不是关于 C# 本身,而是关于 .NET CLR。 - dlev
Chopperdave,这是一个非常有趣的问题,但我想问一下你是否询问了你所意思的 - 对象引用在很大程度上是一个指针,这只是一个“数字”,取决于您的代码运行的系统架构。如果您正在询问 .Net 堆分配的工作原理,那就完全是另外一回事了。 - Russ Clarke
只是想补充一下,这里并没有任何侮辱的意思,我并不是在暗示你不知道自己在说什么 - 问题在于,在 .Net 中这是一个模棱两可的问题,明确我们所讨论的上下文将有助于未来 Stack Overflow 用户的理解。 - Russ Clarke
@RussC 没关系。在这种情况下,我非常愿意承认我不知道自己在问什么 :) - chopperdave
@dlev 谢谢。我会编辑标题和标签来指示。 - chopperdave
2个回答

14

.NET堆栈和堆的工作原理 本文详细介绍了堆和栈的工作原理。

C#和许多其他使用堆的面向对象编程语言在一般引用语境下使用句柄而不是指针。指针类比适用于一些通用概念,但是这种概念模型在像这样的问题上会崩溃。请参阅Eric Lippert在此主题上的优秀帖子Handles are Not Addresses

说一个句柄的大小与指针相同是不合适的(尽管它可能巧合相同)。句柄是对象的别名,它们不必是对对象的正式地址。

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

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

因此,在32位体系结构上,句柄可能是4字节,而在64位体系结构上,它可能是8字节,但这并不是“确定的”,也不是直接与指针相关的。值得注意的是,根据编译器实现和使用的地址范围,某些类型的指针的大小可能会有所不同。

在此背景下,您可能可以通过指针类比来模拟这个问题,但重要的是要意识到句柄不需要是地址。如果CLR想要在未来更改此内容,则CLR的消费者不应该知道任何更好的方法。

这个微妙点的最后一个推动:

这是C#指针:

int* myVariable;

这是一个 C# 句柄:

object myVariable;

它们不是同一个东西。

你可以对指针执行诸如数学运算之类的操作,但不能对句柄(Handle)执行这样的操作。如果你的句柄像指针一样实现,并且你将其用作指针,则在某些方面上误用句柄可能会在以后给你带来麻烦。


3
那不是 C# 指针,因为将托管引用类型作为指针是不合法的。int* 就是 C# 指针类型的一个例子。 - Eric Lippert

13

如果您理解C/C++指针,那么这个答案是最容易理解的。指针简单来说就是某些数据的内存地址。

  1. 对象引用应该与指针的大小相同,在32位CPU上通常为4字节,在64位CPU上为8字节。它的大小不会因其定义位置而改变,但它所在的位置依赖于定义位置。如果它是类的字段,则将驻留在对象所在的堆中。如果它是静态字段,则位于堆的特殊部分,不受垃圾回收的影响。如果它是局部变量,则位于堆栈上。

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

  3. 这是通过指向指针的指针实现的。数据相同-只是一个内存地址。但是,给定内存地址上没有对象。相反,有另一个内存地址,即对对象的原始引用。这就是允许修改引用参数的原因。通常,参数在其方法完成后消失。由于对象的引用不是参数,因此对该引用的更改将保留。引用到引用将消失,但引用不会。这就是传递引用参数的目的。

有一件事需要知道,值类型被存储在原地(没有内存地址,相反它们直接存储在内存地址的位置 - 见#1)。当它们传递给一个方法时,会创建一个副本,并在该方法中使用该副本。当它们通过引用传递时,会传递一个内存地址,该地址可定位于内存中的值类型,从而允许更改。

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

希望我没有把你搞糊涂,如果需要解释,请随时问。


值类型并不总是存储在堆栈上。 - Joshua Enfield
3
价值类型被存储在堆栈上这一说法并不正确。价值类型可以存储在堆栈上,但并非总是如此。例如,在引用类型中的价值类型字段将存储在堆上,即属于它的对象中。无论如何,我还是给你点赞,因为你的答案大部分是正确的... - Thomas Levesque
@ThomasLevesque:已删除关于堆栈上值类型的引用。谢谢! - Kendall Frey
+1 对于一个全面的回答,但正如其他人所说,值类型是例外。然而,我的直觉是,虽然这是 OP 所问的,但可能不是他想要的! - Russ Clarke
非常清晰和全面。谢谢你。幸运的是,很久以前我参加了一门关于C++数据类型的随机课程,并且指针经常被引用(没有双关语)。 - chopperdave
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接