Switch case 奇怪的作用域问题

16

在审查一些第三方C代码时,我遇到了以下内容:

switch (state) {
case 0: 
    if (c=='A') { // open brace
        // code...
    break; // brace not closed!
case 1:
    // code...
    break;
    } // close brace!
case 2:
    // code...
    break;
}

我在审查代码时发现一个错误拼写,但惊讶的是它竟然没有出现编译错误。

这段代码为什么是合法的C语言?
与按预期位置关闭括号相比,对代码执行有什么影响?
是否有任何情况下这种写法会有用处?

编辑:在我查看的示例中,所有的break都存在(如上所述)-但答案也可能包括如果case 0或1中没有break时的行为。


这是因为 switch-case 的奇怪的 goto label 实现。虽然你的特殊情况可能有点奇怪,很难想出一个使用案例(不过可以看看 Duff's Device),但是当你省略 break 时,case 的 fall-through 概念确实非常有用。 - Christian Rau
2
如果你现在正在寻找Duff's device如何工作的解释,这里有一些。 - devnull
在这种情况下,编译器将case 1:视为单独的标签。语法是完全有效的,但从上下文中可以看出,在这种情况下几乎肯定是逻辑错误。通过发送state ==1进行测试,您将看到不正确的结果。 - Chad
2个回答

14

这不仅是有效的,类似的结构已经在真实代码中使用,例如Duff's Device,它是用于复制缓冲区的展开循环:

send(to, from, count)
register short *to, *from;
register count;
{
        register n = (count + 7) / 8;
        switch(count % 8) {
        case 0: do {    *to = *from++;
        case 7:         *to = *from++;
        case 6:         *to = *from++;
        case 5:         *to = *from++;
        case 4:         *to = *from++;
        case 3:         *to = *from++;
        case 2:         *to = *from++;
        case 1:         *to = *from++;
                } while(--n > 0);
        }
}

由于 switch 语句只是计算地址并跳转到它,所以很容易理解为什么它可以与其他控制结构重叠; 其他控制结构内的行也具有可以作为跳转目标的地址!

在您提供的情况下,想象一下如果代码中没有 switchbreak。 当您完成执行 if 语句的 then 部分时,您只需继续进行,因此会直接进入 case 2:。现在,由于您有了switchbreak,因此 break 可以终止哪个部分很重要。根据MSDN页面“C break语句” 的说明:

break 语句终止其所在的最近的 doforswitchwhile 语句的执行。控制传递到终止语句后面的语句。

由于最近的包含块是你的 switch(请注意,if 不在列表中),因此如果您在 then 块内,则转移到 switch 之外。更有趣的是,如果进入 case 0 ,但 c =='A'为 false,那么 if 将控制传递到 then 块的闭合大括号之后,然后开始执行 case 2 中的代码。


2
"c!='A' 情况在你解释的方式下是有意义的 - 但仅凭代码的一瞥,这远非直观!" - Ricibob
1
@Ricibob 我承认,将C视为“高级汇编语言”确实需要一定的观察力。如果你花了一些时间研究C代码是如何编译成汇编/机器代码的,那么这会变得更容易理解。例如,在一个if condition then语句中,只有一个跳转;如果条件为_false_,则跳转到then部分之后。如果你最终进入了then部分,继续执行也会让你到达那里。不需要使用break。然而,在迭代形式中,例如while condition block,块被编译为跳回开始测试... - Joshua Taylor
1
@Ricibob...再次检查条件,然后再次进入块或跳转到块后面。因此,迭代结构需要break,因为循环体会将它们带回开头。switch有点奇怪,因为它不进行迭代。它只需要break,因为程序员通常希望执行一个case而不是穿过其余的case,如果没有break,他们必须在switch语句后面编写一个新标签,并从每个case中使用goto跳转到该标签。无论如何,除非你有...,否则这种控制流交错可能不是好的风格。 - Joshua Taylor
1
@Ricibob...有一个非常好的理由,所以你不太可能在实际应用中看到它,而且你更不太可能去编写它。 - Joshua Taylor
谢谢您的解释。Switch 显然是深不见底的 - 表面上看起来很简单,但在底层可能有很多东西正在发生。让我欣赏在 C# 中简化 switch 所做出的选择。我心里记着永远不要使用这个特定的 switch 结构... - Ricibob

3
在 C 和 C++ 中,只要不跳过任何变量声明,跳入循环和 if 语句块是合法的。可以查看此答案,其中使用了 goto 进行示例,但我认为同样的想法也适用于 switch 块。
语义与 }case 1 上方时的语义不同,这个代码实际上表示如果 state == 0 并且 c != 'A',则转到 case 2,因为那里是 if 语句的结束括号。然后处理该代码并在 case 2 代码的结尾处遇到 break 语句。

我认为只要你不从标识符作用域的外部跳到具有可变修改类型的标识符内部,就可以跳转到包含变量的块中(C11 6.8.4.2p1和6.8.6.1p1)。 - Ian Abbott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接