堆栈上是否有声明类型的对象指针?

时间:2011-02-26 15:28:59

标签: c# .net clr

让我说我有:

class Animal { }
class Dog : Animal { }

在主要方法中:

Dog dog = new Dog();
Animal animal = dog;

现在堆上有一个Dog实例,堆栈上有两个变量,其中存储了相同的引用。下一步是

   //Try to invoke dog.Bark() programatically
   //Try to invoke animal.Bark() programatically

后者在运行时失败,因为animal没有Bark方法。因此,对于这两个变量,堆栈上必定存在不同的东西。导致运行时错误的区别是什么?

2 个答案:

答案 0 :(得分:5)

您正确地推断了许多实施细节。

在进入这些细节之前,有些术语。存储有三种:

  • 非托管存储
  • 托管存储
  • 临时存储

非托管存储超出了您的问题范围。托管存储是我们称之为“堆”的东西。临时存储是“堆栈”或寄存器,如果抖动很聪明且寄存器可用,但作为一个简化的假设,我们可以忽略寄存器,只需将临时存储池称为“堆栈”。

有四种类型:

  • 非托管指针
  • 值类型
  • 引用,又名托管指针
  • 参考类型的实际实例

非托管指针超出了您的问题的范围。 C#允许您直接操作引用(“托管指针”)和值类型;引用类型实例的内容仅通过引用进行操作,并且始终位于CLR实现的堆上。 (没有什么能阻止抖动在堆栈上创建引用类型的实例,如果它有足够的智能来知道对象永远不需要垃圾收集并且不能在方法中存活,但实际上我们的抖动并不是我所知道的。)

根据其生命周期,其他三种东西可以根据需要放在堆栈或堆上。

  

在我的main方法中,创建了一个Dog实例并将其分配给名为dog的变量。我知道堆上发生了什么,假设它位于地址0x00FFFF。

您正在假设(正确地)托管引用在平坦的32位地址空间中实现为地址。 (这实际上不是合法的堆地址,但我们会让它滑动。)当然,CLR的实现不需要使用原始地址作为参考。例如,它们可以是由GC控制的表中的不透明句柄。

  

目前我只知道堆栈中狗的空间值为0x00FFFFF,但我100%肯定狗的空间中的其他位置有其声明类型。

通过它的“声明”类型,我认为你的意思是实例的实际运行时类型,Dog。这是对“声明”的混淆使用。实例未“声明”;实例被“分配”。 变量是“声明的”,变量的声明类型为“Animal”。

是的,在为“new Dog()”分配的实际实例数据中的某处,有一个描述对象运行时类型的“标记”。如果令牌的确切细节对您很重要,请提出另一个问题。

  

我想知道狗的堆栈空间的详细解释,目前我知道那里有一个参考地址,还有什么?

你的问题很模糊,因为堆栈上有两个变量,它们都没有被命名为“dog”。

在运行时,堆栈空间只包含为实例分配的堆空间的托管引用。 C#编译器知道dog1的堆栈空间是编译时类型的managed-reference-to-Animal,而dog2的堆栈空间是编译时类型managed-reference-to-Dog。因此,抖动也知道该信息。抖动在其自己的数据结构中保存有关它的信息;它不会污染堆栈。抖动可以保留此信息以进行优化或用于调试目的。或者,如果它不再需要这些信息,它可以扔掉它。

这又是一个实现细节;抖动完全可以自由地将任何附加信息放在堆栈上。

答案 1 :(得分:1)

引用位于堆栈上,CLR知道它正在使用的引用类型。引用指向的内存在堆上分配。在您的示例中,两个引用都可以指向内存中的相同位置,但CLR因引用类型而以不同方式解释。