在C语言中使用switch语句的while循环

4

我最近看到了下面的代码片段,本以为会出现语法错误,但令我惊讶的是,这段代码产生了有效的输出。

#include <stdio.h>

int main(void) {
    int x = 2;

    switch(x) {
    case 1: printf("1"); break;

        do {
            case 2: printf("2 "); break;
            case 3: printf("3 "); break;
        } while(++x < 4);

        case 4: printf("4"); break;
    }
    return 0;
}

output: 2 4

编译器:GCC 6.3

我发现一个相似的问题,但它并不能完全证明上述条件, C中混合使用'switch'和'while'

有人能解释一下:

  1. 这里到底发生了什么?
  2. 为什么不是语法错误?
  3. 为什么case '3'被跳过了?

2
Stack Overflow中的一个评论指向了另一个有关Duff's Device的问题。它是有效的C代码,因为case标签的语法允许它们作为任何语句的前缀。编译器仅将它们实现为跳转指令(在其抽象机器中)。 - Eric Postpischil
2个回答

5

case X: some_statement;是一个标记语句(6.8.1),就像goto_label: some_statement;一样,唯一的注意点是case/default标签只能出现在switch的主体内(可能在任意嵌套的复合语句中)。这使得case语句在语法上与switch松散耦合。

从语义上讲,switch可实现为计算的goto,与常规的goto一样,它们可以跳转到任何地方(在C11中,您无法跳过VLA声明),包括循环内部(请参见https://en.wikipedia.org/wiki/Duff%27s_device#Mechanism以了解另一种描述)。

在您的示例中,由于breakcase 3:被跳过,但case 4:确实执行,因为case 3:后面的break是一个循环中断的break,而不是一个跳出switchbreakbreak/continue始终适用于它们可以适用的最近结构)。


如果我理解正确,case '2' 中的 break 已经导致了循环的终止,并且在 case '4' 中执行了 fall through 操作,最终退出了 switch。 - TruthSeeker
@prasadM 是的。如果你想轻松地查看像Duff's device这样的代码,那么就不要考虑与case紧密耦合的switches和fallthrough,只需将case标签视为一种特殊类型的goto标签,它只能在switches中使用。有了这个,fall-through就很自然了。do{/*...*/}while(/*...*/);内的break会直接跳到分号后面的位置。接下来是一个标记为case 4:的语句。它会被执行,因为它只是一个带标签的语句,所以代码不会跳到其他地方。 - Petr Skocik

0

switch 语句的官方 C 语法如下:

switch ( expression ) statement

任何 statement 都可以是标记语句,其语法包括:

case constant-expression : statement

这意味着你几乎可以在 switch 的主体内放置任何想要的内容: 它可以是一个包含多个语句和一个包含多个语句的 do-while 语句的复合语句,并且 case 标签可以前缀于其中的任何语句。

编译器仅使用跳转指令(在其抽象机器内)实现 switch。例如,像 do-while 这样的循环语句是通过测试控制表达式并有条件地跳转来实现的。因此,尽管在结构化语言中有一个好的结构,但最终归结为跳转指令,而这些指令可以按任意顺序交织在一起。

跳转并不是最令人困惑的部分,对象初始化和生命周期才更令人担忧。如果不小心处理,switch语句可能会无意中跳过对象的初始化。

由于控制从switch跳转到case 2然后中断,退出do-while循环,所以printf中的“3”永远不会被执行。这使代码停留在case 4语句处,打印“4”,然后跳出switch语句。


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