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();
}
}
注意这个类不必被密封才能工作。
所以看起来好像编译器会发出一个调用,如果所有这些都是真的:
上一篇: Call and Callvirt