gpt4 book ai didi

.net - 调用和 Callvirt

转载 作者:行者123 更新时间:2023-12-03 05:47:30 26 4
gpt4 key购买 nike

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

最佳答案

当运行时执行call指令时,它会调用一段确切的代码(方法)。毫无疑问它存在于何处。 IL 经过 JIT 处理后,调用站点生成的机器代码就是无条件 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 的密封 类比非密封类具有更好的方法分派(dispatch)性能。

编辑:事实证明我错了。 C# 编译器无法无条件跳转到方法的位置,因为对象的引用(方法内 this 的值)可能为 null。相反,它会发出 callvirt 来执行 null 检查并在需要时抛出异常。

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

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

编译器可能会发出 this 指针 (local0) 具有空值的可验证代码,只有 csc 不会这样做。

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

鉴于此信息,现在在我看来,sealed 仅对 API 安全有用。我发现another question这似乎表明密封您的类没有任何性能优势。

编辑 2:这比看起来要复杂得多。例如,以下代码发出 call 指令:

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

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

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

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

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

不幸的是,我的电脑目前无法运行,但一旦它再次启动,我会尝试一下。

关于.net - 调用和 Callvirt,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/193939/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com