MSIL或本机代码中“检测到无法访问的代码”

时间:2011-12-27 11:10:38

标签: c# warnings unreachable-code

编译器是否在运行时编译MSIL或本机代码中的“无法访问代码”?

3 个答案:

答案 0 :(得分:13)

这个问题有点不清楚,但我会对它进行一次拍摄。

首先,Adam的答案是正确的,因为编译器根据“优化”开关是打开还是关闭而发出的IL存在差异。编译器在使用优化开关去除无法访问的代码时更加积极。

有两种相关的无法访问的代码。首先是 de jure 无法访问的代码;也就是说,C#语言规范称之为无法访问的代码。其次,有事实无法访问的代码;这是C#规范没有调用为无法访问的代码,但无法访问。在后一类无法访问的代码中,优化器已知无法访问的代码,并且优化器无法知道代码无法访问。

编译器通常会始终删除 de jure 无法访问的代码,但只有在启用优化程序时才会删除事实无法访问的代码。

以下是每个例子:

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都无法访问。第一个是 de jure 无法访问; C#编译器声明为了明确的赋值检查,必须将此代码视为无法访问。

后两个 de jure 可以访问但事实上无法访问。必须检查它们是否有明确的分配错误,但允许优化器删除它们。

在两者中,优化器检测到(2)情况但不检测(3)情况。优化器知道整数乘以零始终为零,因此条件始终为false,因此它会删除整个语句。

在(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);

编译器不会抱怨,即使 de jure 可以访问对Console.WriteLine的调用。我在C#3.0中解决了这个问题,然后我们进行了改变。

请注意,我们保留随时更改无法访问的代码检测器和代码生成器的工作方式的权利;我们可能决定总是发出无法访问的代码,或者永远不会发出它或其他什么。

答案 1 :(得分:3)

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
}

简单,干净,优化。

答案 2 :(得分:0)

在发布模式下,某些代码块可能会被编译出来,但这不是该错误消息的含义。所以,要回答你的问题,代码如:

if (false)
 // do something
除非你启用了调试,否则

永远不会进入字节码(这是因为如果你附加一个调试器,你可以手动进入if语句,所以代码需要在那里)。

当您收到由于无法访问代码而无法继续调试的错误消息时,这往往意味着您正在调试的进程与您正在使用的源代码(不同版本等等)不对齐