简体   繁体   中英

Why does callvirt IL instruction cause recursive invocation in virtual methods?

IL doesn't always use callvirt instruction for virtual methods in a case like this:

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

In this case, it is said that IL will produce call instead of callvirt where callvirt is produced to check whether variable is null or not and throws NullReferenceException otherwise.

  1. Why does a recursive invocation happen till stack overflow if callvirt is used instead of call ?
  2. If call is used, then when does it check whether the instance variable it uses to call the methods is null or not?

Why does a recursive invocation happen till stack overflow if callvirt is used instead of call?

Because then your code is exactly the same as:

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

Which clearly is an infinite recursion, provided that the method given is the most-overriding version of ToString.

If call is used, then how come it checks whether the instance variable it uses to call the methods is null or not?

The question is not answerable because the question assumes a falsehood. The call instruction does not check to see if the reference to the receiver is null or not, so asking why the call instruction checks for null doesn't make any sense.

Let me rephrase that for you into some better questions:

Under what circumstances does the C# compiler generate a call vs a callvirt?

If the C# code is doing a non virtual call on a virtual method then the compiler must generate a call, not a callvirt. The only time this happens really is when using base to call a virtual method.

If the C# code is doing a virtual call then the compiler must generate a callvirt.

If the C# code is doing a non virtual call on a non virtual method then the compiler can choose to generate either call or callvirt. Either will work. The C# compiler typically chooses to generate a callvirt.

The call instruction does not automatically do a null check, but callvirt does. If the C# compiler chooses to generate a call instead of a callvirt, is it also obligated to generate a null check?

No. The C# compiler can skip the null check if the receiver is already known to not be null. For example, if you said (new C()).M() for a non-virtual method M then it would be legal for the compiler to generate a call instruction without a null check. We know that (1) the method is not virtual, so it does not have to be a callvirt ; we can choose whether to use callvirt or not. And we know (2) that new C() is never null, so we do not have to generate a null check.

If the C# compiler does not know that the receiver is not null, then it will either generate a callvirt, or it will generate a null check followed by a call.

  1. callvirt will call the MakeMeASandwich implementation, not the Object implementation. This is how you get your stack overflow.

  2. The initial call was with callvirt, which establishes that the reference is not null. If control is inside this ToString implementation, you already know there is an object.

  1. call is used because it is known right there which method to call, and it should not be looked up at runtime ( callvirt would cause the code to call the method defined at the most specific class, which then causes your stack overflow).

  2. callvirt implies a null check, whereas call does not.

Callvirt calls the most derived method available. In this case this is MakeMeASandwich.ToString().

The purpose of callvirt is not only to check for null, but also to perform a virtual method call.

Why does a recursive invocation happen till stack overflow if callvirt is used instead of call?

As others have answered, callvirt calls the method virtually. It's as if you had written

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

If call is used, then how come it checks whether the instance variable it uses to call the methods is null or not?

Others have mentioned that you already know that this is not null. That's incorrect. If callvirt is used to call MakeMeASandwich.ToString , then yes, this cannot be null. However, there is no requirement that callvirt is used to call your function. Other languages do allow you to write the call in such a way that call opcode is generated, and in that case, no null check is performed. I think C++/CLI allows it with makeMeASandwich->MakeMeASandwich::ToString() , but I'm not entirely sure. You can't know for sure that this isn't null unless you check.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM