コンパイラは実行時に MSIL またはネイティブ コードの "到達不能コード" をコンパイルしますか?
3 に答える
質問は少し不明確ですが、私はそれを試してみます。
まず、「最適化」スイッチがオンかオフかによってコンパイラが出力する IL に違いがある限り、Adam の答えは正しいです。コンパイラは、最適化スイッチをオンにして、到達不能なコードを削除することについて、より積極的です。
関連する 2 種類の到達不能コードがあります。1 つ目は、当然のことながら到達不能なコードです。つまり、C# 言語仕様でunreachableと呼ばれるコードです。第二に、事実上到達不能なコードがあります。これは、C# 仕様では到達不能とは規定されていませんが、到達できないコードです。後者の種類の到達不能コードには、オプティマイザが到達不能であると認識しているコードと、オプティマイザが到達不能であると認識していないコードがあります。
通常、コンパイラは、法律上到達できないコードを常に削除しますが、オプティマイザーがオンになっている場合は、事実上到達できないコードのみを削除します。
それぞれの例を次に示します。
int x = 123;
int y = 0;
if (false) Console.WriteLine(1);
if (x * 0 != 0) Console.WriteLine(2);
if (x * y != 0) Console.WriteLine(3);
3 つの Console.WriteLines はすべて到達不能です。1 つ目は、法律上到達不能です。C# コンパイラは、明確な代入チェックのために、このコードを到達不能として扱う必要があると述べています。
2 番目の 2 つは、法律上は到達可能ですが、事実上は到達不可能です。明確な代入エラーがないかチェックする必要がありますが、オプティマイザはそれらを削除できます。
2 つのうち、オプティマイザーは (2) のケースを検出しますが、(3) のケースは検出しません。オプティマイザーは、0 を掛けた整数は常に 0 であり、したがって条件は常に false であることを認識しているため、ステートメント全体を削除します。
(3) の場合、オプティマイザは y に割り当てられる可能性のある値を追跡せず、乗算の時点で y が常にゼロであると判断します。あなたと私は結果が到達不能であることを知っていますが、オプティマイザーはそれを知りません。
明確な代入チェックについては、次のようになります。到達不能なステートメントがある場合、すべてのローカル変数はそのステートメントで代入されていると見なされ、すべての代入は発生しないと見なされます。
int z;
if (false) z = 123;
Console.WriteLine(z); // Error
if (false) Console.WriteLine(z); // Legal
最初の使用法は、使用時に z が明確に割り当てられていないため、不正です。2 番目の使用法は、コードに到達できないため、違法ではありません。z は、割り当てられる前に使用することはできません。
C# 2 には、2 種類の到達可能性を混同するいくつかのバグがありました。C# 2 では、これを行うことができます。
int x = 123;
int z;
if (x * 0 != 0) Console.WriteLine(z);
また、当然のことながらConsole.WriteLineへの呼び出しが到達可能であっても、コンパイラは文句を言いません。私は C# 3.0 でそれを修正し、重大な変更を行いました。
到達不能コード検出器とコード生成器の動作をいつでも変更する権利を留保することに注意してください。到達不能なコードを常に発行するか、発行しないかなどを決定する場合があります。
C#コンパイラは、デバッグ構成またはリリース構成のいずれかでアプリケーションをコンパイルできます。これにより、到達不能コードがCILにコンパイルされ、出力実行可能ファイルに出力されるかどうかの動作が変更されます。たとえば、簡単な関数を見てみましょう。
public static int GetAnswer()
{
return 42;
Console.WriteLine("Never getting here!");
}
これをデバッグ構成でコンパイルすると、メソッド全体(到達不能コードを含む)がCILとして発行されます。これは次のようになります(おそらく、nop
デバッグを支援するためのいくつかの追加の指示があります):
.method public static int32 GetAnswer() cil managed
{
.maxstack 1
.locals init (int32)
ldc.i4.s 42 // load the constant 42 onto the stack
stloc.0 // pop that 42 from the stack and store it in a local
br.s L_000C // jump to the code at L_000C
ldstr "Never getting here!" // load the string on the stack
call void [mscorlib]System.Console::WriteLine(string) // call method
L_000C: ldloc.0 // push that 42 onto the stack from the local
ret // return, popping the 42 from the stack
}
このすべてのコードが出力される理由は、デバッガーが到達不能コードに手動でステップインできるようにするためです。おそらく、デバッグ環境でコードを強制的に実行するためです。
そうは言っても、リリース構成でプロジェクトをビルドすると、コンパイラーは、ビルドされたアセンブリがデバッガーでステップスルーされないため、到達不能コードを出力しないことを認識します。CILは次のようになります。
.method public static int32 GetAnswer() cil managed
{
.maxstack 1
ldc.i4.s 42 // load the constant 42 onto the stack
ret // return, popping the 42 from the stack
}
シンプル、クリーン、そして最適化。
リリース モードでは、特定のコード ブロックがコンパイルされることがありますが、それはエラー メッセージの意味ではありません。したがって、質問に答えるには、次のようなコードを実行します。
if (false)
// do something
デバッグを有効にしない限り、バイトコードにはなりません (これは、デバッガーをアタッチすると、その if ステートメントに手動でステップインできるため、コードがそこにある必要があるためです)。
コードに到達できないため、デバッグを続行できないというエラー メッセージが表示された場合は、デバッグしているプロセスが、使用しているソース コードと一致していない (異なるバージョンなど) ことを意味する傾向があります。