什么编译器考虑switch语句?

时间:2013-03-07 20:32:45

标签: c# switch-statement unreachable-code

再次受到-5问题的启发!

我阅读this comment的[@Quartermeister]并感到惊讶!

为什么这会编译

switch(1) {
    case 2:
}

但事实并非如此。

int i;

switch(i=1) {
    case 2: // Control cannot fall through from one case label ('case 2:') to another
}

这两个

switch(2) {
    case 2: // Control cannot fall through from one case label ('case 2:') to another
}

更新:

-5问题变为-3

3 个答案:

答案 0 :(得分:21)

应该编译。 C#规范要求switch部分至少有一个语句。解析器应该禁止它。

让我们忽略解析器允许空语句列表的事实;这不是什么相关的。规范说开关部分的末端必须没有可达到的终点;这是相关的一点。

在上一个示例中,交换机部分有一个可到达的终点:

void M(int x) { switch(2) { case 2: ; } }

所以一定是个错误。

如果你有:

void M(int x) { switch(x) { case 2: ; } }

然后编译器不知道x是否会是2.它保守地假定它可以,并且说该部分有一个可到达的终点,因为交换机案例标签是可达的。

如果你有

void M(int x) { switch(1) { case 2: ; } }

然后,编译器可以推断端点不可访问,因为无法访问案例标签。编译器知道常量1永远不会等于常量2。

如果你有:

void M(int x) { switch(x = 1) { case 2: ; } }

void M(int x) { x = 1; switch(x) { case 2: ; } }

然后你知道并且我知道终点是不可达的,但编译器不知道这一点。规范中的规则是可达性仅通过分析常量表达式来确定。包含变量的任何表达式,即使您通过其他方式知道其值,也不是常量表达式。

在过去,C#编译器存在错误,而事实并非如此。你可以这样说:

void M(int x) { switch(x * 0) { case 2: ; } }

并且编译器会认为x * 0必须为0,因此无法访问案例标签。那是一个bug,我在C#3.0中修复过。规范说只有常量用于该分析,x是变量,而不是常量。

现在,如果程序是 legal ,那么编译器可以使用这样的高级技术来影响生成的代码。如果你这样说:

void M(int x) { if (x * 0 == 0) Y(); }

然后编译器可以像编写

一样生成代码
void M(int x) { Y(); }

如果它想要的话。但是,为了确定语句可达性,它不能使用x * 0 == 0为真的事实。

最后,如果你有

void M(int x) { if (false) switch(x) { case 2: ; } }

然后我们知道交换机不可达,因此该块没有可达到的终点,所以这是令人惊讶的合法。但鉴于上面的讨论,你现在知道了

void M(int x) { if (x * 0 != 0) switch(x) { case 2: ; } }

不会将x * 0 != 0视为false,因此终点被视为可到达。

答案 1 :(得分:2)

在Visual Studio 2012中,第一个原因显而易见。编译器确定代码无法访问:

switch (1)
{
    case 2:
}

警告:检测到无法访问的代码。

在另外两种情况下,编译器报告"控制不能从一个案例标签('案例2:')转到另一个"。我没有看到它说"(' case 1')"在任何一个失败的案件中。

我猜编译器对于持续评估并不积极。例如,以下内容是等效的:

int i;
switch(i=1)
{
    case 2:
}

int i = 1;
switch(i)
{
    case 2:
}

在这两种情况下,编译器都会尝试生成代码,当 进行评估并确定您正在编写的内容时:

switch (1)
{
    case 2:
}

确定代码无法访问。

我怀疑"为什么不编译"答案将是"因为我们让JIT编译器处理积极的优化。"

答案 2 :(得分:1)

好吧,问题就在于编译器完全优化了开关,这里有证明:

static void withoutVar()
{
    Console.WriteLine("Before!");

    switch (1)
    {
        case 2:
    }

    Console.WriteLine("After!");
}

当用ILSpy反编译时,向我们展示了这个IL:

.method private hidebysig static 
    void withoutVar () cil managed 
{
    // Method begins at RVA 0x2053
    // Code size 26 (0x1a)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldstr "Before!"
    IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    IL_000b: nop
    IL_000c: br.s IL_000e

    IL_000e: ldstr "After!"
    IL_0013: call void [mscorlib]System.Console::WriteLine(string)
    IL_0018: nop
    IL_0019: ret
} // end of method Program::withoutVar

在任何地方都没有回忆起switch语句。我认为它没有优化第二个的原因可能与运算符重载和排序有关。因此,我可能有自定义类型,当分配到1时,它会变为2。但是,我不完全确定,在我看来应该提交错误报告。