编译器是否在运行时编译 MSIL 或本机代码中的“无法访问的代码”?
3 回答
这个问题有点不清楚,但我会试一试。
首先,亚当的回答是正确的,因为编译器根据“优化”开关是打开还是关闭发出的 IL 有所不同。在打开优化开关的情况下,编译器会更加积极地删除无法访问的代码。
有两种相关的不可访问代码。首先是法律上无法访问的代码;也就是说,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);
所有三个 Console.WriteLines 都无法访问。第一个是法律上无法到达的;C# 编译器声明必须将此代码视为不可访问,以便进行明确的分配检查。
后两个在法律上是可达的,但实际上是不可达的。必须检查它们是否有明确的分配错误,但允许优化器删除它们。
在这两者中,优化器检测到 (2) 情况,而不是 (3) 情况。优化器知道整数乘以零始终为零,因此条件始终为假,因此它会删除整个语句。
在 (3) 的情况下,优化器不会跟踪分配给 y 的可能值并确定 y 在乘法点处始终为零。即使您和我都知道结果是无法达到的,但优化器并不知道这一点。
关于明确赋值检查的内容是这样的:如果你有一个无法访问的语句,那么所有局部变量都被认为是在该语句中赋值,并且所有赋值都被认为没有发生:
int z;
if (false) z = 123;
Console.WriteLine(z); // Error
if (false) Console.WriteLine(z); // Legal
第一次使用是非法的,因为 z 在使用时没有明确分配。第二种用法并不违法,因为代码甚至无法访问;z 在分配之前不能使用,因为控制永远不会到达那里!
C# 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 语句,因此代码需要在那里)。
当您收到由于无法访问的代码而无法继续调试的错误消息时,这往往意味着您正在调试的进程与您正在使用的源代码(不同的版本等)不一致。