2013-01-11 33 views
0

可能重复:
Why is the C# compiler emitting a callvirt instruction for a GetType() method call?为什么编译器为非虚拟方法发出callvirt?

我看到,当我把一个类的实例方法的C#编译器发出 callvirt指令调用该方法,为什么会这样?

是否表示编译器将所有实例方法视为virtual methods,这有什么神秘之处?

+1

'callvirt'具有执行'null'检查的好处;请注意,在某些情况下,编译器*知道*非虚拟调用具有非空接收器,它将发出“调用”指令。 – dlev

回答

2

简短回答:这样比较安全。

callvirt将首先检查对象是否为空,如果是,则抛出异常。

您会注意到调用静态方法仍将使用call,因为对象不能为空。

Here's有点历史。

12

它在那里实现了在C#语言规范中做出的承诺。其中说,通过空引用调用类的实例方法是不合法的。这可能听起来像是一个明显的特征,但它在OOP语言中实际上并不常见。特别是C++/CLI编译器没有它。 CLI规范没有它。像C++这样的非托管语言没有它。

当实例方法没有使用任何非静态类成员时,它甚至会有一个好的结果。这样的方法当然应该是静态的,但这不是必需或强制的。

C#的要求非常好,它使NullReferenceException更容易诊断。由于它们是在调用站点而不是在实例方法内部生成的,因此它将阐明对象引用为空。搞清楚这个引用在一个方法里面是null是有点困难的,特别是因为你看不到它。更复杂的是地址实际上不为空,因此访问某个类的字段将生成一个从0偏移的地址。如果该对象很大,超过64千字节,则这又是不安全。在这样一个大对象的末尾访问一个字段不一定会产生处理器异常,你只会阅读随机垃圾。或者如果你写的话会损坏内存。

所以C#团队寻找一种廉价的方法来实现空测试。并在callvirt IL指令中找到一个。与call,不同,承诺在CLI规范中有一个例外。一个非常便宜的测试,它只需要一个机器代码指令。并且不需要分支,如果处理器的分支预测逻辑猜测错误,那就非常昂贵。

现在你也知道为什么String.Equals()包含此位神秘代码:

public override bool Equals(Object obj) { 
    if (this == null) 
     throw new NullReferenceException(); 
    // etc... 
} 
+1

http://ideone.com/lHukZ5下面是关于null的C++调用的链接。 –

12

汉斯和小李的答案是正确的。我想补充的额外的一点点信息:

  • callvirt指令明确记录为法律上的非虚拟方法精确,让你得到空校验行为。

  • 在极少数情况下,C#编译器证明非虚拟调用不可能有空接收方,它会回退到call并保存执行空检查所需的纳秒。例如,如果您有(new C()).InstanceMethod()那么应该生成call而不是callvirt,因为编译器知道接收表达式永远不为null。 (如果分配失败,则抛出异常,因此将不会执行该呼叫。)

+0

是newobj +调用C#编译器不使用callvirt的唯一实例吗? – Tinister

+0

@tinister:no。有很多表达式很容易被看作是非空的。 –

+0

'+ 1'就这样你就可以打200k :) –

相关问题