call と callvirt その5

5月の記事の続き。続きを書く日が来るとは自分でも驚き。IL のお話です。

インスタンスメソッドを呼ぶときにコンパイラが call を生成したり、callvirt を生成したりします。その違いは何よ?とずっと疑問だったんですが、その答えが!

vir http://blogs.msdn.com/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspx

インスタンスの NULL チェックが不要なときは call だそうな。逆に NULL チェックを強制したいときが callvirt。NULL チェックの奇妙な機械語の件が The Old New Thing で取り上げられています。

NULL チェックが不要なときの例 : (new Foo()).FooNonVirtualMethod()
ちょっと実験。

using System;

class Program
{
    static void Main()
    {
        Hoge hoge = new Hoge();
        Console.WriteLine( hoge.Huga() );
        Console.WriteLine( new Hoge().Huga() );
    }
}

public class Hoge
{
    public string Huga() { return "HogeHuga"; }
}

IL。Main だけ。

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // コード サイズ       33 (0x21)
  .maxstack  1
  .locals init ([0] class Hoge hoge)
  IL_0000:  newobj     instance void Hoge::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  callvirt   instance string Hoge::Huga()
  IL_000c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0011:  newobj     instance void Hoge::.ctor()
  IL_0016:  call       instance string Hoge::Huga()
  IL_001b:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0020:  ret
} // end of method Program::Main

IL_0007 は callvirt ですが、IL_0016 は確かに call になっています。
へぇなトリビアですね。

ちなみにエリックさんの同じエントリでC#コンパイラのバグでスレッドのデッドロックに至る可能性があるバグが告白されていたり。3.0でも健在だって。