在switch-case中有效但无价值的语法?

时间:2017-01-18 19:02:58

标签: c switch-statement language-lawyer

通过一个小错字,我偶然发现了这个结构:

int main(void) {
    char foo = 'c';

    switch(foo)
    {
        printf("Cant Touch This\n");   // This line is Unreachable

        case 'a': printf("A\n"); break;
        case 'b': printf("B\n"); break;
        case 'c': printf("C\n"); break;
        case 'd': printf("D\n"); break;
    }

    return 0;
}

似乎printf语句顶部的switch有效,但也完全无法访问。

我得到了一个干净的编译,甚至没有关于无法访问代码的警告,但这似乎毫无意义。

编译器是否应将此标记为无法访问的代码? 这有什么用途吗?

8 个答案:

答案 0 :(得分:225)

也许不是最有用的,但完全毫无价值。您可以使用它来声明switch范围内可用的局部变量。

switch (foo)
{
    int i;
case 0:
    i = 0;
    //....
case 1:
    i = 1;
    //....
}

标准(N1579 6.8.4.2/7)包含以下示例:

  

示例在人工程序片段中

switch (expr)
{
    int i = 4;
    f(i);
case 0:
    i = 17;
    /* falls through into default code */
default:
    printf("%d\n", i);
}
     

标识符为i的对象存在自动存储持续时间(在块内)但永远不存在   初始化,因此如果控制表达式具有非零值,则调用printf函数将   访问不确定的值。同样,无法访问函数f

P.S。 BTW,该示例不是有效的C ++代码。在那种情况下(N4140 6.7/3,强调我的):

  

从具有自动存储持续时间的变量不在范围内的点跳过 90 的程序   它在范围内的点是格式错误的 ,除非该变量具有标量类型 ,具有普通默认值的类类型   构造函数和一个普通的析构函数,这些类型之一的cv限定版本,或其中一个的数组   前面的类型 并在没有初始值设定项的情况下声明 (8.5)。

           

90)从switch陈述条件到案件标签的转移在这方面被视为跳跃。

因此,将int i = 4;替换为int i;会使其成为有效的C ++。

答案 1 :(得分:98)

  

这是否可以用于任何目的?

是。如果不是声明,而是在第一个标签之前放置一个声明,这可能是完全合理的:

switch (a) {
  int i;
case 0:
  i = f(); g(); h(i);
  break;
case 1:
  i = g(); f(); h(i);
  break;
}

声明和语句的规则一般是为块共享的,所以它允许那样允许在那里使用语句的规则。

值得一提的是,如果第一个语句是循环结构,则case标签可能出现在循环体中:

switch (i) {
  for (;;) {
    f();
  case 1:
    g();
  case 2:
    if (h()) break;
  }
}

如果有更可读的写法,请不要编写这样的代码,但它完全有效,f()调用是可以访问的。

答案 2 :(得分:39)

有一个名为Duff's Device的着名用法。

int n = (count+3)/4;
switch (count % 4) {
  do {
    case 0: *to = *from++;
    case 3: *to = *from++;
    case 2: *to = *from++;
    case 1: *to = *from++;
  } while (--n > 0);
}

这里我们将from指向的缓冲区复制到to指向的缓冲区。我们复制了count个数据实例。

do{}while()语句在第一个case标签之前开始,case标签嵌入do{}while()

这会使do{}while()循环结束时的条件分支数减少大约4倍(在此示例中,可以将常量调整为您想要的任何值)。

现在,优化器有时可以为您执行此操作(特别是如果它们正在优化流/矢量化指令),但如果没有配置文件引导优化,他们无法知道您是否期望循环变大。

通常,变量声明可以在那里出现并在每种情况下使用,但在切换结束后超出范围。 (注意任何初始化都将被跳过)

此外,不是特定于交换机的控制流可以让您进入交换机块的该部分,如上所示,或者使用goto

答案 3 :(得分:15)

假设您在Linux上使用gcc,如果您使用的是4.4或更早版本,它会给您一个警告。

-Wunreachable-code选项was removed in gcc 4.4以后。

答案 4 :(得分:11)

不仅用于变量声明,还用于高级跳跃。当且仅当您不喜欢意大利面条代码时,您可以很好地利用它。

int main()
{
    int i = 1;
    switch(i)
    {
        nocase:
        printf("no case\n");

        case 0: printf("0\n"); break;
        case 1: printf("1\n"); goto nocase;
    }
    return 0;
}

打印

1
no case
0 /* Notice how "0" prints even though i = 1 */

应该注意的是,switch-case是最快的控制流条款之一。所以程序员必须非常灵活,有时会涉及这样的情况。

答案 5 :(得分:11)

应该注意的是,switch语句中的代码几乎没有结构限制,或者case *:标签放在此代码中的位置*。这使得duff's device之类的编程技巧成为可能,其中一种可能的实现方式如下:

int n = ...;
int iterations = n/8;
switch(n%8) {
    while(iterations--) {
        sum += *ptr++;
        case 7: sum += *ptr++;
        case 6: sum += *ptr++;
        case 5: sum += *ptr++;
        case 4: sum += *ptr++;
        case 3: sum += *ptr++;
        case 2: sum += *ptr++;
        case 1: sum += *ptr++;
        case 0: ;
    }
}

您看,switch(n%8) {case 7:标签之间的代码绝对可以访问...

* As supercat thankfully pointed out in a comment:自C99以来,goto或标签(无论是否为case *:标签)都不会出现在包含的声明范围内VLA声明。因此,对case *:标签的展示位置存在 no 结构限制是不正确的。然而,duff的设备早于C99标准,并且它无论如何都不依赖于VLA。然而,我觉得不得不插入一个"虚拟"因此而进入我的第一句话。

答案 6 :(得分:10)

您得到了与required gcc option -Wswitch-unreachable相关的答案以生成警告,此答案是详细说明可用性 / 值得部分

直接引用C11,章节§6.8.4.2,(强调我的

switch (expr)
{
int i = 4;
f(i);
case 0:
i = 17;
/* falls through into default code */
default:
printf("%d\n", i);
}
     

标识符为i的对象存在自动存储   持续时间(在块内)但从未初始化,因此如果   控制表达式具有非零值,调用printf   函数将访问不确定的值。同样,呼吁   无法访问函数f

这是非常不言自明的。您可以使用它来定义本地范围的变量,该变量仅在switch语句范围内可用。

答案 7 :(得分:9)

可以实现一个"循环和一半"有了它,虽然它可能不是最好的方法:

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));