在描述非虚方法调用的内部时,Richter是错误的吗?

nig*_*der 12 .net clr jit

我就直接写这个问题杰弗里里希特,但最后时刻他没有回答我:)所以我会尽量在这里得到你的帮助的答案,球员:)

杰弗里写道,在第9版"CLR via C#"一书中,第108页写道:

void M3() {
  Employee e;
  e = new Manager();
  year = e.GetYearsEmployed();
  ...
}
Run Code Online (Sandbox Code Playgroud)

M3中的下一行代码调用Employee的非虚拟实例GetYearsEmployed方法.调用非虚拟实例方法时,JIT编译器将查找与用于进行调用的变量类型对应的类型对​​象.在这种情况下,变量e被定义为Employee.(如果Employee类型没有定义被调用的方法,则JIT编译器将类层次结构向下移向Object以寻找此方法.它可以这样做,因为每个类型对象都有一个引用其基类型的字段;信息未在图中显示.)然后,JIT编译器在类型对象的方法表中查找引用被调用方法的条目,JIT方法(如果需要),然后调用JITted代码.

当我第一次读到这篇文章时,我认为在JIT-ting期间沿着类层次结构寻找方法是没有效果的.在编译阶段很容易找到该方法.但我相信杰弗里.我在另一个论坛上发布了这个信息,另一个人证实了我的疑问,这是奇怪的,并且无效,而且似乎是错误的信息.

实际上,如果您在反编译器中查找相应的IL代码,例如ILDasm或Reflector(我已经检入过),您将看到IL有一个callvirt指令从基类调用该方法,因此JIT不会需要查看方法在运行时所在的类:

public class EmployeeBase
{
    public int GetYearsEmployed() { return 1; }
}

public class Employee : EmployeeBase
{
    public void SomeOtherMethod() { }
}

public class Manager : Employee
{
    public void GenProgressReport() { }
}

...

Employee e;
e = new Manager();
int years = e.GetYearsEmployed();
Run Code Online (Sandbox Code Playgroud)

产生的IL是:

L_0000: nop 
L_0001: newobj instance void TestProj.Form1/Manager::.ctor()
L_0006: stloc.0 
L_0007: ldloc.0 
L_0008: callvirt instance int32 TestProj.Form1/EmployeeBase::GetYearsEmployed()
Run Code Online (Sandbox Code Playgroud)

你看?编译器已经发现该方法不在Employee类中,而是在EmployeeBase类中,并且发出了正确的调用.但是从Richter的话来说,JIT必须找出该方法实际上是在运行时位于EmployeeBase类中.

杰弗里里希特错了吗?或者我不明白什么?

小智 0

根据我的理解,并使用你的例子:在引擎盖下:

基类中的虚拟方法将在派生类方法表中拥有一个条目。这意味着“对象”类型中的所有虚拟方法在其所有派生类方法表中都可用。

派生类中没有提供功能的非虚拟方法(如示例代码中)实际上不会在派生类方法表中拥有条目!

为了检查这一点,我在 WinDbg 中运行代码来检查Manager类的方法表。

MethodDe JIT 名称

506a4960 503a6728 PreJIT System.Object.ToString()

50698790 503a6730 PreJIT System.Object.Equals(System.Object)

50698360 503a6750 PreJIT System.Object.GetHashCode()

506916f0 503a6764 PreJIT System.Object.Finalize()

001b00c8 00143904 JIT 管理器..ctor()

0014c065 001438f8 无 Manager.GenProgressReport()

因此,我可以看到对象的虚拟对象方法,但看不到实际的方法 GetYearsEmployed,因为它不是虚拟的并且没有派生实现。顺便说一句,根据相同的概念,您在派生类中也看不到 SomeOtherMethod 函数。

但是,您可以调用这些函数,只是它们不在方法表中。我可能是错误的,但我相信调用堆栈会被遍历来找到它们。也许这就是里克特先生在他的书中的意思。我发现他的书很难读,但那是因为概念很复杂,而且他比我聪明:)

我不确定 IL 是否反映了该问题。我相信它可能是 IL 下面的一层,这就是我使用 Windbg 来查看的原因。我想你可以使用 Windbg 来查看它在堆栈中的行走情况......