読者です 読者をやめる 読者になる 読者になる

AccessViolation Exception

仕事でもはんだづけ、家でもはんだづけ

ILでFizzBuzzを書く

.NETをコンパイルするときの中間言語(IL)を直接書き下してFizzBuzzを書いてみます。

メモ

ldc.i4//int32ロード  
ldloc//ローカル変数のロード  
ldstr//文字列のロード  
call//メソッド呼び出し  
stloc//ローカル変数にストア  
bgt//value1 > value2なら分岐  
rem//剰余  
brfalse//0,nullなら分岐  
box//値型をボクシング  
br//強制分岐  

FizzBuzz in IL

f:id:kamiyaowl:20150117002258p:plain

c#で書くと

int i, n;
Console.Write("fizzbuzz max>");
n = Int32.Parse(Console.ReadLine());

for (i = 0; i < n; i++) {
    if (i % 15 == 0) {
        Console.WriteLine("FizzBuzz");
    } else if (i % 5 == 0) {
        Console.WriteLine("Buzz");
    } else if (i % 3 == 0) {
        Console.WriteLine("Fizz");
    } else {
        Console.WriteLine(i);
    }
}

微妙に固く書きましたがILと似たような感じ(ILは表示させる物をobjectに格納してConsole.WriteLineに投げてる

IL DASMでばらしてみると

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // コード サイズ       136 (0x88)
  .maxstack  2
  .locals init ([0] int32 i,
           [1] int32 n,
           [2] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldstr      "fizzbuzz max>"
  IL_0006:  call       void [mscorlib]System.Console::Write(string)
  IL_000b:  nop
  IL_000c:  call       string [mscorlib]System.Console::ReadLine()
  IL_0011:  call       int32 [mscorlib]System.Int32::Parse(string)
  IL_0016:  stloc.1
  IL_0017:  ldc.i4.0
  IL_0018:  stloc.0
  IL_0019:  br.s       IL_007f
  IL_001b:  nop
  IL_001c:  ldloc.0
  IL_001d:  ldc.i4.s   15
  IL_001f:  rem
  IL_0020:  ldc.i4.0
  IL_0021:  ceq
  IL_0023:  ldc.i4.0
  IL_0024:  ceq
  IL_0026:  stloc.2
  IL_0027:  ldloc.2
  IL_0028:  brtrue.s   IL_0039
  IL_002a:  nop
  IL_002b:  ldstr      "FizzBuzz"
  IL_0030:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0035:  nop
  IL_0036:  nop
  IL_0037:  br.s       IL_007a
  IL_0039:  ldloc.0
  IL_003a:  ldc.i4.5
  IL_003b:  rem
  IL_003c:  ldc.i4.0
  IL_003d:  ceq
  IL_003f:  ldc.i4.0
  IL_0040:  ceq
  IL_0042:  stloc.2
  IL_0043:  ldloc.2
  IL_0044:  brtrue.s   IL_0055
  IL_0046:  nop
  IL_0047:  ldstr      "Buzz"
  IL_004c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0051:  nop
  IL_0052:  nop
  IL_0053:  br.s       IL_007a
  IL_0055:  ldloc.0
  IL_0056:  ldc.i4.3
  IL_0057:  rem
  IL_0058:  ldc.i4.0
  IL_0059:  ceq
  IL_005b:  ldc.i4.0
  IL_005c:  ceq
  IL_005e:  stloc.2
  IL_005f:  ldloc.2
  IL_0060:  brtrue.s   IL_0071
  IL_0062:  nop
  IL_0063:  ldstr      "Fizz"
  IL_0068:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_006d:  nop
  IL_006e:  nop
  IL_006f:  br.s       IL_007a
  IL_0071:  nop
  IL_0072:  ldloc.0
  IL_0073:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0078:  nop
  IL_0079:  nop
  IL_007a:  nop
  IL_007b:  ldloc.0
  IL_007c:  ldc.i4.1
  IL_007d:  add
  IL_007e:  stloc.0
  IL_007f:  ldloc.0
  IL_0080:  ldloc.1
  IL_0081:  clt
  IL_0083:  stloc.2
  IL_0084:  ldloc.2
  IL_0085:  brtrue.s   IL_001b
  IL_0087:  ret
} // end of method Program::Main

IL_001c ~ IL_0028を見ればわかるのですが、

i % 15
== 0
== 0
結果を一度変数に格納
割り切れてなかったらジャンプ

のような一見無駄がありそうなコードに変換されているようにもみえます。それとelseでジャンプするようにもなっています。

ちなみにILで書いたコードもビルドするなりプロジェクトに加えてexternするなりすればc#コードと混在して使用できます。