c# - 如何使用堆中的类型对象来定位结构体实例的虚拟方法?

标签 c# memory-management clr heap-memory stack-memory

下面是书中的代码示例,用于显示何时对值类型进行装箱:

internal struct Point 
{
   private readonly Int32 m_x, m_y;
   public Point(Int32 x, Int32 y) {
      m_x = x;
      m_y = y;
   }
   
   //Override ToString method inherited from System.ValueType
   public override string ToString() {
      return String.Format("({0}, {1})", m_x.ToString(), m_y.ToString());
   }
}

class Program
{
    static void Main(string[] args) {
       Point p1 = new Point(10, 10);
       p1.ToString();       
    }
}

作者说:

In the call to ToString, p1 doesn’t have to be boxed. At first, you’d think that p1 would have to be boxed because ToString is a virtual method that is inherited from the base type, System.ValueType. Normally, to call a virtual method, the CLR needs to determine the object’s type in order to locate the type’s method table. Because p1 is an unboxed value type, there’s no type object pointer. However, the just-in-time (JIT) compiler sees that Point overrides the ToString method, and it emits code that calls ToString directly (nonvirtually) without having to do any boxing. The compiler knows that polymorphism can’t come into play here because Point is a value type, and no type can derive from it to provide another implementation of this virtual method.

我有点明白它的意思了,因为PointSystem.ValueType重写了ToString,CLR不需要检查类型对象来定位类型的方法表,编译器可以发出直接调用 ToString 的 IL 代码。很公平。

但假设 p1 还从 System.ValueType 调用 GetHashCode,如下所示:

class Program
{
    static void Main(string[] args) {
       Point p1 = new Point(10, 10);
       p1.ToString();  
       p1.GetHashCode();     
    }
}

由于 Point 结构不会从 System.ValueType 重写 GetHashCode(),因此编译器这次无法直接发出 IL 代码,并且 CLR需要定位类型的方法表来查找GetHashCode方法,但是正如作者所说,p1是一个未装箱的值类型,没有类型对象指针,那么CLR如何查找GetHashCode堆中 Point 结构体类型对象中的 方法?

最佳答案

如果我们查看生成的 MSIL,我们会看到以下内容:

IL_0000:  ldloca.s    00 // p1
IL_0002:  ldc.i4.s    0A 
IL_0004:  ldc.i4.s    0A 
IL_0006:  call        System.Drawing.Point..ctor
IL_000B:  ldloca.s    00 // p1
IL_000D:  constrained. System.Drawing.Point
IL_0013:  callvirt    System.Object.ToString
IL_0018:  pop         
IL_0019:  ldloca.s    00 // p1
IL_001B:  constrained. System.Drawing.Point
IL_0021:  callvirt    System.Object.GetHashCode
IL_0026:  pop     

我们查一下ECMA-335 Part III.2.1约束上。:

The constrained. prefix is permitted only on a callvirt instruction. The type of ptr must be a managed pointer (&) to thisType. The constrained prefix is designed to allow callvirt instructions to be made in a uniform way independent of whether thisType is a value type or a reference type.

If thisType is a value type and thisType implements method then
ptr is passed unmodified as the ‘this’ pointer to a call of method implemented by thisType

If thisType is a value type and thisType does not implement method then
ptr is dereferenced, boxed, and passed as the ‘this’ pointer to the callvirt of method

This last case can only occur when method was defined on System.Object, System.ValueType, or System.Enum and not overridden by thisType. In this last case, the boxing causes a copy of the original object to be made, however since all methods on System.Object, System.ValueType, and System.Enum do not modify the state of the object, this fact cannot be detected.

所以,是的,这确实会导致装箱,但仅当没有覆盖时,因为 System.Object 方法需要一个类,而不是一个值类型。但如果它被重写,则该方法的 this 指针必须是托管指针,与任何其他值类型方法相同。

关于c# - 如何使用堆中的类型对象来定位结构体实例的虚拟方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65931791/

相关文章:

c - 16 KB 由 OS X 上的 getc 和 fprintf 分配

c# - C# 未初始化的变量危险吗?

c# - 自定义控件的文本框字符串/文本的填充

c# - 如何设置ListView的ItemsSource?

c - 必须知道声明函数指针的结构偏移量

c# - 类型定义和类型引用有什么区别?

C# 类型对象指针

c# - IList<T>.FindIndex(Int32, Predicate <T>)

c# - 可以从类库 C# 中输出到控制台吗?

c# - 我应该使用 ReAllocHGlobal 还是 FreeHGlobal/AllocHGlobal?