switch (...) while (0) 的目的是什么?

4
我最近发现 switch (...) while (0) {} 在C语言中是合法的(在这里),但我找不到关于这个东西的解释。 我在互联网上唯一看到这种情况的另一个例子是在一个 混淆 GitHub 代码库 中,没有任何解释。
所有我的研究显然给出了关于while或switch循环的结果,甚至没有提及这种语法,所以我猜它更多是合法但非常罕见和可能无用的东西,滥用了标准。 有人能帮我理解这个吗?
编辑: 正如 @phuclv 的答案中所解释的那样,switch语句期望紧随其后的是选择语句,可以是带有一些代码的括号(在这种情况下...可能是case语句)或者带有自己括号的循环等,这意味着在C语言中这是合法的:
switch (...) while (...) switch (...) {}

switch 完全不关心后面跟着的语句,它似乎只是寻找 case 和/或 default。

switch (1) while (0) {
    puts("Not executed");
}

puts语句不会被执行,因为没有case/default语句,所以这个switch基本上是无用的。 你可以在Compiler Explorer上查看,GCC会发出警告并删除了switch语句。

然而,请注意:

#include <stdio.h>

int main(void) {
    switch (1) while (1) switch (0) {
        case 1:
        puts("hello");
    }
}

什么都没有显示出来,程序立即退出,因为 switch (1) 没有 case 1 或 default 语句。 如果我们添加一个:
switch (1) case 1: while (1) switch (0)

程序无限循环,因为最嵌套的循环是 switch (0),没有 case 0 或 default。
结论:while (0) 只是一种滥用,并且除了混淆外没有任何实用性,但这仍然是一个有趣的事情要知道。

1
你是在问它是做什么的(链接已经解释了),还是为什么被允许? - HolyBlackCat
1
你是在问它是做什么的(链接已经解释了),还是为什么它被允许? - undefined
1
这没有什么实际意义,它只是利用/展示了C语言的switch语句只是局部跳转,并不真正关心其他任何事情,所以你可以将其与其他C语法交错使用。另一个实际有用的演示是Duff's Device,但它更复杂,因为它实际上会执行操作,所以核心洞察力不太明确。 - Masklinn
1
@Masklinn Duff的设备在现代编程中是否实际有用是非常值得质疑的,它已经过时了。 - Lundin
1
在语法上有效并不意味着语法具有目的。这就是你在问题中引用答案的要点。这只是开关结构语法定义/实现的“ emergent behaviour”。它之所以发生,是因为没有明确地阻止,而不是因为有意为之。要防止这种情况需要一种更复杂的语言定义和更复杂的编译器,但却没有特定的好处。 - Clifford
显示剩余3条评论
2个回答

8
在C标准中,语句被定义为以下之一。

A.2.3 Statements

(6.8) statement:

labeled-statement
compound-statement
expression-statement
selection-statement
iteration-statement
jump-statement

switch是一个选择语句,其语法定义如下

(6.8.4) selection-statement:

if ( expression ) statement
if ( expression ) statement else statement
switch ( expression ) statement
所以 `switch` 接收一个 `expression` 并执行 `statement`,通常是 compound-statement {},但也可以是任何语句,包括 iteration-statement
(6.8.2) compound-statement: ``` { block-item-listopt } ```
(6.8.5) iteration-statement: ``` while ( expression ) statement do statement while ( expression ) ; for ( expressionopt ; expressionopt ; expressionopt ) statement for ( declaration expressionopt ; expressionopt ) statement ```

switch语句会跳转到labeled-statement(也是一个普通语句),它不关心子语句内的内容,编译器只会生成一个计算跳转到匹配expressioncase

(6.8.1) labeled-statement:

identifier : statement
case constant-expression : statement
default : statement

0
基本上,这个建筑
switch(expr) while (cond) {
}

应该几乎等同于:
while(cond) {
    switch(expr) {
    }
}

除了评估condexpr的顺序之外。

只需检查以下代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    switch(random() % 6) while(1) {
        case 0: printf("You got a one(1)\n");
        case 1: printf("You got a two(2)\n");
        case 2: printf("You got a three(3)\n");
        case 3: printf("You got a four(4)\n");
        case 4: printf("You got a five(5)\n");
        case 5: printf("You got a six(6)\n");
    }
}

运行时,它会给你类似于:

You got a four(4)
You got a five(5)
You got a six(6)
You got a one(1)
You got a two(2)
You got a three(3)
You got a four(4)
You got a five(5)
You got a six(6)
You got a one(1)
...

编辑

很抱歉,我的帖子输出实际上并不是非常随机 :) 所以,恐怕等价性远非真实。

在使用while(cond) switch(expr)进行检查后,我得到了一个更或多或少的随机序列,所以由于某种原因,在使用前一种结构时expr没有完全计算。

在使用switch(expr) while(cond)进行检查后,我没有得到任何随机性,因为random()只计算一次,并产生所示的输出(第一个随机数恰好是0,选择第一个情况,然后按顺序选择所有其他情况,循环回到while循环)。在这种情况下,在switch中使用break;没有用处,因为它会跳出循环,使您看到的行为是循环执行直到break;语句,然后退出循环)


你真的需要那个建筑吗? - Luis Colorado
1
看到我对答案的修改了吗?你给了我一个提示,让我明白为什么第一种方法会产生一些随机性,而第二种方法则不会。将break;语句改成goto end;(并在最后一个case标签后面加上一个end标签),你就能得到你想要的输出结果了。 - Luis Colorado
我现在更加理解了,这是一个有趣的案例。我将使用调试器来跟踪一些包含棘手开关的代码的控制流程。 - Chi_Iroh
1
可能最好的方法是检查编译器将代码组装成什么样子。在我看来,在while循环之外放置switch语句没有公平的应用。switch似乎只会评估一次...然后下一次通过选择第一个case语句。有点奇怪。 - Luis Colorado
1
可能最好的方法是检查编译器将代码组装成什么样子。在我看来,在while循环外部放置switch语句并不公平。switch似乎只评估一次...然后下一次通过会选择第一个case语句。有点奇怪。 - undefined
显示剩余5条评论

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