Call和Callvirt

CIL指令“Call”和“Callvirt”之间有什么区别?


call用于调用非虚拟,静态或超类方法,即调用的目标不受覆盖。 callvirt用于调用虚拟方法(因此,如果this是覆盖方法的子类,则调用子类版本)。


当运行时执行一个call指令时,它会调用一段精确的代码(方法)。 毫无疑问,它存在于何处。 一旦IL被打出,在呼叫地点产生的机器码就是无条件的jmp指令。

相反, callvirt指令用于以多态方式调用虚拟方法。 方法代码的确切位置必须在运行时为每个调用确定。 由此产生的JITted代码涉及通过vtable结构的一些间接性。 因此调用执行起来较慢,但它更灵活,因为它允许多态调用。

请注意,编译器可以为虚拟方法发出call指令。 例如:

sealed class SealedObject : object
{
   public override bool Equals(object o)
   {
      // ...
   }
}

考虑调用代码:

SealedObject a = // ...
object b = // ...

bool equal = a.Equals(b);

虽然System.Object.Equals(object)是一个虚拟方法,但在此用法中, Equals方法的重载无法存在。 SealedObject是一个密封类,不能有子类。

由于这个原因,.NET的sealed类可以比非sealed类具有更好的方法调度性能。

编辑:原来我错了。 C#编译器不能作出无条件跳转到该方法的位置,因为这个对象的引用(该值this方法内)可能为空。 相反,它会发出执行空检查的callvirt ,并在需要时抛出。

这实际上解释了我在.NET框架中使用Reflector发现的一些奇怪的代码:

if (this==null) // ...

编译器可能发出可验证的代码,该代码对于this指针(local0)具有空值,只有csc不会执行此操作。

所以我猜call只用于类的静态方法和结构。

鉴于这些信息,现在看来, sealed只对API安全有用。 我发现另一个问题似乎表明,封闭课程没有任何性能优势。

编辑2:这似乎比这更多。 例如下面的代码发出一个call指令:

new SealedObject().Equals("Rubber ducky");

很显然,在这种情况下,对象实例不可能为null。

有趣的是,在DEBUG构建中,以下代码发出callvirt

var o = new SealedObject();
o.Equals("Rubber ducky");

这是因为你可以在第二行设置一个断点并修改o的值。 在发布版本中,我认为这个调用将是一个call而不是callvirt

不幸的是,我的个人电脑目前无法使用,但一旦它再次出现,我就会尝试一下。


由于这个原因,.NET的密封类可以比非密封类具有更好的方法调度性能。

不幸的是,这种情况并非如此。 Callvirt还有其他一些有用的东西。 当一个对象有一个方法调用它时,callvirt将检查对象是否存在,如果不是,则抛出一个NullReferenceException。 即使对象引用不存在,调用也会跳转到内存位置,并尝试执行该位置中的字节。

这意味着callvirt总是被C#编译器(不确定VB)用于类,并且调用总是用于结构体(因为它们不能为空或者子类)。

编辑作为对Drew Noakes的回应评论:是的,你似乎可以让编译器发出任何类的调用,但仅限于以下特定情况:

public class SampleClass
{
    public override bool Equals(object obj)
    {
        if (obj.ToString().Equals("Rubber Ducky", StringComparison.InvariantCultureIgnoreCase))
            return true;

        return base.Equals(obj);
    }

    public void SomeOtherMethod()
    {
    }

    static void Main(string[] args)
    {
        // This will emit a callvirt to System.Object.Equals
        bool test1 = new SampleClass().Equals("Rubber Ducky");

        // This will emit a call to SampleClass.SomeOtherMethod
        new SampleClass().SomeOtherMethod();

        // This will emit a callvirt to System.Object.Equals
        SampleClass temp = new SampleClass();
        bool test2 = temp.Equals("Rubber Ducky");

        // This will emit a callvirt to SampleClass.SomeOtherMethod
        temp.SomeOtherMethod();
    }
}

注意这个类不必被密封才能工作。

所以看起来好像编译器会发出一个调用,如果所有这些都是真的:

  • 方法调用在创建对象后立即生效
  • 该方法没有在基类中实现
  • 链接地址: http://www.djcxy.com/p/31499.html

    上一篇: Call and Callvirt

    下一篇: Java initializing object design