6

在这种情况下, IL 并不总是使用方法callvirt指令:virtual

class MakeMeASandwich{
  public override string ToString(){
    return base.ToString();
  }
}

在这种情况下,据说 IL 将产生call而不是产生callvirtwherecallvirt来检查是否variable存在null,否则抛出NullReferenceException

  1. callvirt如果使用而不是,为什么递归调用会发生直到堆栈溢出call
  2. 如果call使用,那么它什么时候检查它用来调用方法的实例变量是否为空?
4

5 回答 5

10

如果使用 callvirt 而不是调用,为什么递归调用会发生直到堆栈溢出?

因为那么您的代码与以下内容完全相同:

override string ToString()
{
    return this.ToString();
}

这显然是一个无限递归,只要给定的方法是最重要的 ToString 版本。

如果使用 call ,那么它如何检查用于调用方法的实例变量是否为空?

这个问题是无法回答的,因为这个问题是假的。call 指令不检查对接收者的引用是否为 null,因此询问call 指令为什么检查 null没有任何意义。

让我把它改写成一些更好的问题:

C#编译器在什么情况下生成调用vs callvirt?

如果 C# 代码对虚拟方法执行非虚拟调用,则编译器必须生成调用,而不是 callvirt。真正发生这种情况的唯一一次是在使用调用虚拟方法时。base

如果 C# 代码正在执行虚拟调用,则编译器必须生成一个 callvirt。

如果 C# 代码对非虚拟方法执行非虚拟调用,则编译器可以选择生成 call 或 callvirt。要么会工作。C# 编译器通常选择生成 callvirt。

call 指令不会自动进行空值检查,但 callvirt 会。如果 C# 编译器选择生成调用而不是 callvirt,是否也有义务生成空检查?

否。如果已知接收器不为空,C# 编译器可以跳过空检查。例如,如果您说(new C()).M()的是非虚拟方法 M,那么编译器生成call没有空值检查的指令是合法的。我们知道 (1) 方法不是虚拟的,所以它不必是callvirt; 我们可以选择是否使用callvirt。而且我们知道 (2)new C()永远不会为空,因此我们不必生成空检查。

如果 C# 编译器知道接收者不为 null,那么它将生成一个 callvirt,或者生成一个 null 检查,然后进行调用。

于 2012-04-18T16:18:04.477 回答
3
  1. callvirt 将调用 MakeMeASandwich 实现,而不是 Object 实现。这就是你如何让你的堆栈溢出。

  2. 最初的调用是使用 callvirt,它确定引用不为空。如果控件在此 ToString 实现中,则您已经知道有一个对象。

于 2012-04-18T16:12:12.640 回答
3
  1. call使用它是因为它在那里知道要调用哪个方法,并且不应该在运行时查找它(callvirt会导致代码调用在最具体的类中定义的方法,从而导致堆栈溢出)。

  2. callvirt意味着一个空检查,而call没有。

于 2012-04-18T16:12:26.037 回答
2

Callvirt 调用最可用的派生方法。在这种情况下,这是 MakeMeASandwich.ToString()。

callvirt 的目的不仅仅是为了检查null,还为了执行一个虚方法调用。

于 2012-04-18T16:12:19.373 回答
2

如果使用 callvirt 而不是调用,为什么递归调用会发生直到堆栈溢出?

正如其他人所回答的那样,callvirt虚拟地调用该方法。好像你写过

public override string ToString() {
    return ToString();
}

如果使用 call ,那么它如何检查用于调用方法的实例变量是否为空?

其他人提到你已经知道那this不是空的。这是不正确的。如果 callvirt用于调用MakeMeASandwich.ToString,则是,this不能为空。但是,没有callvirt用于调用您的函数的要求。其他语言确实允许您以生成操作码的方式编写调用,call在这种情况下,不执行空值检查。我认为 C++/CLI 允许它使用makeMeASandwich->MakeMeASandwich::ToString(),但我不完全确定。除非您检查,否则您无法确定它this不为空。

于 2012-04-18T16:18:26.890 回答