CIL や Java バイトコードなどのスタックベースの中間言語にローカル変数があるのはなぜですか? スタックのみを使用できます。手作りの IL にとってはそれほど簡単ではないかもしれませんが、コンパイラーは確実にそれを行うことができます。しかし、私の C# コンパイラはそうではありません。
スタック変数とローカル変数はどちらもメソッドに対してプライベートであり、メソッドが戻ると範囲外になります。したがって、メソッドの外部 (別のスレッド) から見える副作用とは何の関係もありません。
私が正しければ、JIT コンパイラはマシン コードを生成するときにスタック スロットとローカル変数の両方へのロードとストアを排除するため、JIT コンパイラはローカル変数の必要性も認識しません。
一方、C# コンパイラは、最適化を有効にしてコンパイルした場合でも、ローカル変数のロードとストアを生成します。なんで?
たとえば、次の不自然なサンプル コードを見てみましょう。
static int X()
{
int a = 3;
int b = 5;
int c = a + b;
int d;
if (c > 5)
d = 13;
else
d = 14;
c += d;
return c;
}
最適化を使用して C# でコンパイルすると、以下が生成されます。
ldc.i4.3 # Load constant int 3
stloc.0 # Store in local var 0
ldc.i4.5 # Load constant int 5
stloc.1 # Store in local var 1
ldloc.0 # Load from local var 0
ldloc.1 # Load from local var 1
add # Add
stloc.2 # Store in local var 2
ldloc.2 # Load from local var 2
ldc.i4.5 # Load constant int 5
ble.s label1 # If less than, goto label1
ldc.i4.s 13 # Load constant int 13
stloc.3 # Store in local var 3
br.s label2 # Goto label2
label1:
ldc.i4.s 14 # Load constant int 14
stloc.3 # Store in local var 3
label2:
ldloc.2 # Load from local var 2
ldloc.3 # Load from local var 3
add # Add
stloc.2 # Store in local var 2
ldloc.2 # Load from local var 2
ret # Return the value
4 つのローカル変数へのロードとストアに注意してください。ローカル変数を使用せずに、まったく同じ操作を (明らかな定数伝播の最適化を無視して) 記述できます。
ldc.i4.3 # Load constant int 3
ldc.i4.5 # Load constant int 5
add # Add
dup # Duplicate top stack element
ldc.i4.5 # Load constant int 5
ble.s label1 # If less than, goto label1
ldc.i4.s 13 # Load constant int 13
br.s label2 # Goto label2
label1:
ldc.i4.s 14 # Load constant int 14
label2:
add # Add
ret # Return the value
それは私には正しいようで、はるかに短く、より効率的です。では、なぜスタックベースの中間言語にはローカル変数があるのでしょうか? そして、なぜ最適化コンパイラはそれらをそれほど広範囲に使用するのでしょうか?