式のトレース (3)
せっかくなので、最初の課題の MSIL を読み解いてみたいと思います。
まずは、検証に使った C# コード(該当メソッドのみ)はこちら。
static void Main() { var i = 0; i *= ~i & 2 | 1 * (++i) - -(i--) ^ i; Console.WriteLine("i = {0}", i); }
続いて .NET Reflector で該当プログラムの MSIL を取得した結果。
( VS で MSIL を見る方法を知らないので。。。 ^^;; )
.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 6 .locals init ( [0] int32 num) L_0000: nop L_0001: ldc.i4.0 L_0002: stloc.0 L_0003: ldloc.0 L_0004: ldloc.0 L_0005: not L_0006: ldc.i4.2 L_0007: and L_0008: ldloc.0 L_0009: ldc.i4.1 L_000a: add L_000b: dup L_000c: stloc.0 L_000d: ldloc.0 L_000e: dup L_000f: ldc.i4.1 L_0010: sub L_0011: stloc.0 L_0012: neg L_0013: sub L_0014: ldloc.0 L_0015: xor L_0016: or L_0017: mul L_0018: stloc.0 L_0019: ldstr "i = {0}" L_001e: ldloc.0 L_001f: box int32 L_0024: call void [mscorlib]System.Console::WriteLine(string, object) L_0029: nop L_002a: ret }
このままだと判りにくいので、各行毎に、変数とスタック領域の値の遷移を表にしてみます。
※ 0000 は初期化 (nop) なので省略、 0019 以降は出力処理なので省略してあります。
※「 - 」は何もセットされていない状態を示します。(「 0 」がセットされている状態と明確に区別したかったので分けました)
Line | i | stack1 | stack2 | stack3 | stack4 | stack5 | stack6 |
---|---|---|---|---|---|---|---|
0001 | - | 0 | - | - | - | - | - |
0002 | 0 | - | - | - | - | - | - |
0003 | 0 | 0 | - | - | - | - | - |
0004 | 0 | 0 | 0 | - | - | - | - |
0005 | 0 | 0 | -1 | - | - | - | - |
0006 | 0 | 0 | -1 | 2 | - | - | - |
0007 | 0 | 0 | 2 | - | - | - | - |
0008 | 0 | 0 | 2 | 0 | - | - | - |
0009 | 0 | 0 | 2 | 0 | 1 | - | - |
000a | 0 | 0 | 2 | 1 | - | - | - |
000b | 0 | 0 | 2 | 1 | 1 | - | - |
000c | 1 | 0 | 2 | 1 | - | - | - |
000d | 1 | 0 | 2 | 1 | 1 | - | - |
000e | 1 | 0 | 2 | 1 | 1 | 1 | - |
000f | 1 | 0 | 2 | 1 | 1 | 1 | 1 |
0010 | 1 | 0 | 2 | 1 | 1 | 0 | - |
0011 | 0 | 0 | 2 | 1 | 1 | - | - |
0012 | 0 | 0 | 2 | 1 | -1 | - | - |
0013 | 0 | 0 | 2 | 2 | - | - | - |
0014 | 0 | 0 | 2 | 2 | 0 | - | - |
0015 | 0 | 0 | 2 | 2 | - | - | - |
0016 | 0 | 0 | 2 | - | - | - | - |
0017 | 0 | 0 | - | - | - | - | - |
0018 | 0 | - | - | - | - | - | - |
……相変わらず判りにくいか。 ^^;
それでは上から読み解いてみます。
0001 〜 0002
0001 でスタック上に「 0 」を格納して、 0002 でローカル変数 (i) に代入しています。
(「 var i = 0 」の部分)
0003
最後の復号代入演算に使用するために i の値をスタック上にコピーします。
# ってことで、「最後の、複合代入演算子によって、後の演算結果に関係なくゼロが乗算の左辺になる」訳ですね。
……なので、これ以降のロジックは本質的に無意味なんですが、折角なので右辺の演算も見てみます。
0004 〜 0005
0004 で i の値をスタック上にコピーして、 0005 で NOT 演算を行っています。 (~i)
0006 〜 0007
0006 でスタック上に「 2 」を格納して、 0007 で AND 演算を行っています。 (&)
0008 〜 000c
0008 で i の値をスタック上にコピー、 0009 でスタック上に「 1 」を格納して、 000a で二数を足しています。
これによってインクリメントを行っています。 (++i)
更に 000b でスタック上の値を複写して、 000c で i に代入しています。
( i への代入前に複写するのは、 i に代入した上で次の計算にその値を利用するため)
000d 〜 0011
000d で i の値をスタック上にコピー、 000e で複写をします。
(ここで複写しているのは、デクリメント前の値を後続の計算で使用するため)
その後、 000f でスタック上に「 1 」を格納して、 0010 で二数を引きます。
これによってデクリメントを行っています。 (i--)
更に 0011 で i に代入しています。
0012
ここで符号反転をしています。 (-)
0013
スタック上の二数を引きます。 (-)
0014 〜 0015
0014 で i の値をスタック上にコピー、 0015 で XOR 演算を行っています。 (^)
※ここで判る様に、 XOR 演算子で使用される i は i-- の結果になります。
0016
OR 演算を行っています。
演算の対象は、 (~i & 2) の結果と ((++i) - -(i--) ^ i) の結果になります。
(~i & 2) は 0004 〜 0007 の結果から 2 、 ((++i) - -(i--) ^ i) の結果は 0008 〜 0015 の結果から 2 になるので、 OR の結果は 2 です。
0017 〜 0018
最後に復号代入演算が行われます。
0017 で 0003 でコピーした i の値と 0016 までの右辺演算結果をかけています。
0003 でコピーした i は 0 だったので、計算結果は 0 です。
そして、 0018 で結果 (0) を i に代入して完了です。
……ふぅ、長かった。