2013-03-07 55 views
16

再次受到-5问题的启发!什么是关于switch-statement的编译器思考?

我读的@Quartermeister [this comment]和已经惊讶!

那么,为什么这个编译

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

+1

编译器可能会优化第一个,但由于赋值而无法删除第二个。 – 2013-03-07 20:34:51

+0

我的猜测是'1'是编译时已知的一个常量表达式,让编译器优化整个开关,而'i = 1'是一个非常量表达式(尽管编译器也知道产生一个特定的值),所以编译器试图保持开关。 – dasblinkenlight 2013-03-07 20:35:39

+0

在理想的世界中,编译器可能应该接受或拒绝这两者。它当然不应该为它们生成任何代码。但在这个世界上,我认为这只是“阿甘正传效应的另一个例子:”愚蠢是愚蠢的“:) Q:为什么浪费时间/ braincells甚至讨论它? – paulsm4 2013-03-07 20:37:40

回答

21

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

让我们忽略解析器允许空语句列表的事实;这不是什么相关的。该规范指出,切换部分的结尾不得有可到达的终点;这是相关的一点。

在你的最后一个例子,开关部分具有到达终点:

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,因此,本案的标签是不可达。那是我在C#3.0中修复的一个错误。该规范说只有常数用于该分析,而x是一个变量,而不是一个常数。

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

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 != 0false,所以终点认为可以达到的。

+0

谢谢。我测试了'switch(x){}',它编译,不是吗? – 2013-03-07 23:49:39

+0

@KenKin:这是合法的;允许开关包含零开关部分。 – 2013-03-08 00:06:29

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。但是,我并不完全确定,在我看来应该像提交bug报告一样。

+0

“...优化了循环,”...什么循环?我认为你的意思是转换。 – 2013-03-07 21:20:53

+0

@JimMischel好点,固定。 – 2013-03-07 21:36:26

+2

这不是一个错误;请不要提交报告。 – 2013-03-07 21:45:16

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编译器处理激进的优化”。

+0

谢谢。我修改了。 – 2013-03-07 23:30:32