【问题标题】:Reference types - can we see the actual reference?引用类型——我们能看到实际的引用吗?
【发布时间】:2012-01-11 12:23:08
【问题描述】:

由于不了解值类型的变量实际包含什么,因此初学者通常会混淆引用类型和值类型之间的区别。我们知道:

  • 值类型存储实际
  • 引用类型只存储对象的引用

是否可以检查每种变量以查看值或实际引用本身?引用是否存储为某种编码值?我知道引用可以按值传递,所以我假设是这样。

我认为这将有助于新人的理解,并且非常有趣。

【问题讨论】:

  • 你会看到的唯一的东西是一个地址,一个看似随机的数字。你为什么想看这个?
  • 巩固变量中的不是他们设置的对象,而是一个地址的想法。我们将如何访问它?
  • @m.edmonson,对您的问题感到困惑。在 .NET 中,对象的物理地址在每次 GC 执行标记和清除时都会发生变化,这是基本的。这是一种短暂的品质,开发人员对此并不感兴趣。
  • 当您将类型的声明与变量的声明混为一谈时,初学者也会感到困惑。 “class C{}”是一个类型的声明。 “C c;”是变量的声明。同样,说“值类型将值存储在声明中”是令人困惑的。不。值类型不存储任何内容,并且声明是源代码编辑器中的一大块文本。值类型的变量 存储值。它将它存储在与变量关联的存储中
  • @Dykam, Garry Vass:我在 StackOverflow 上的第一讨厌是人们说“你不应该想知道那个”。为什么要积极扼杀好奇心?要么回答问题,要么别管 OP!

标签: c# .net vb.net clr il


【解决方案1】:

是否可以检查每种变量以查看值或实际引用本身?

澄清一下,引用类型变量的 引用。参考就是价值。

引用是一种值,就像 int 是一种值一样。与 int 不同,引用是一个只能复制取消引用的值;你不能直接在 C# 中观察它的值,因为它的值是垃圾收集器的一个实现细节。

引用是否存储为某种编码值?

是的,没错。实际上,引用是一个 32 位或 64 位整数(取决于您是在 32 位还是 64 位进程中),它是指向垃圾收集器已知的与被引用对象的数据相关联的某个结构的指针.

如果您想直接查看引用,那么执行此操作的工具就是调试器。将 C# 代码加载到调试器中,对其进行编译、运行、下断点,然后查看堆栈和寄存器的状态。稍微聪明一点,您应该能够找出哪些堆栈位置和寄存器对应于哪些局部变量。值类型的局部变量对应的位置将包含值;那些引用类型将包含看起来像指针的值。如果您检查内存窗口中的这些指针,您将看到由垃圾收集器维护的描述对象内容的结构。

【讨论】:

  • 我认为重要的是要注意,虽然现有的 .net 实现可以将(对象信息记录的)地址存储在引用变量中,但不能保证未来的版本会这样做。例如,参考变量的某些位可能是选择多个堆之一的索引,而其他位是该堆内的索引。虽然这样的系统对于现有处理器可能效率低下,但围绕这样的模型设计未来的处理器可能会提高缓存利用率。现有的 .net 代码不应该关心这些细节。
  • @supercat:当然。事实上,引用可以实现为完全不透明的句柄,只有在它们索引到垃圾收集器拥有的某个表时才有意义;没有理由说明引用中的 any 位需要具有特定含义。碰巧的是,在今天的实现中,引用的每一位都是有意义的,因为它可以被解释为一个指针。但这随时可能发生变化。
  • 出于好奇,虽然现有的 CPU 可能不允许这样的事情非常高效,但我想知道在未来的 CPU 中是否值得为数据为 8 的对象创建一个小对象堆(可能 16)字节或更少,其中对象表中的每个槽都将保存实际的对象数据,而不是指向它的指针。在现有的 CPU 上,每个堆访问都需要额外的指令来确定任何给定引用属于哪个堆,但是如果有一个“获取对象地址”指令使用引用中的位来选择单或双间接...
  • ...这似乎可以在访问小对象时大大提高效率。因为我猜想一个典型的运行程序中可能有一半的对象实例将是 16 个字节或更少,如果不是 8 个,那么为每个对象访问消除额外的内存获取似乎是一个巨大的胜利。知道是否有任何新的 CPU 可能支持这样的事情吗?
  • @supercat:这些想法很有趣。我对硬件设计知之甚少;我的大部分交互都是与虚拟机进行的。将这些问题交给 CPU 架构专家会很有趣。
【解决方案2】:

这可能是 Jon Skeet 的一个,但我可能有不同的角度:

不用担心太多这些东西在内存中的表示方式。除非您通读了整个语言规范——否则谁会这样做? - 你真的不需要知道。真的。不要费心记住哪些数据存储在哪里 - 很可能,这是特定于实现的。

相反,考虑语义,例如传递给函数的值类型是复制的,而引用类型是引用的。诸如此类。

您并不真的想知道一个类型的声明实际上包含什么。相信我。您想知道的是它的行为方式

【讨论】:

  • +1 这里是 Eric Lippert says 同样的事情。地址是一个实现细节,语言规范在指定引用时没有提到地址。了解他们的行为方式,而不是他们的工作方式。
  • 虽然我同意你的说法,但我觉得这不是问题的重点。我想他想知道怎么做——而不是你对为什么它不重要的看法。了解复杂功能的工作原理可以让您成为一名更好的工程师,即使您没有立即使用这些知识。
  • 虽然我感谢并同意您的回答,但它并没有解决我的问题的重点。我只想访问该地址只是为了证明它在那里没有其他原因。我同意@AdamG.Carstensen。
  • @AdamG.Carstensen - 我同意你们俩的观点:我并没有真正回答你的问题。但是您确实提到“帮助新人理解”是一个原因,我认为这对新人并没有太大帮助。但如果从 VM 实现的角度来看这一点会很有趣。
【解决方案3】:

你可以很容易地用一个固定的对象做到这一点;

GCHandle gch=GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr AddressInMemory=gch.AddrOfPinnedObject();

【讨论】:

  • 这不适用于non-blittable types,就像几乎所有课程一样。
  • 我的立场是正确的。看起来我唯一一次需要这个是使用原语。
【解决方案4】:

您可以使用unsafe 代码:

    unsafe
    static void Main(string[] args)
    {
        string s = "Hello";

        fixed (char* pc = s)
        {                
            IntPtr p = (IntPtr)pc;
            Console.WriteLine(p);   // here is your meaningless address 
        }            
    }

【讨论】:

  • 这会生成 s 的固定副本,那你为什么不直接固定 s?
  • 我们不会使用s 做任何事情。
  • @EugenRieck 你有这个声明的来源吗?我看这里没有抄袭。
猜你喜欢
  • 2021-05-22
  • 1970-01-01
  • 2021-02-13
  • 2015-08-28
  • 1970-01-01
  • 2020-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多