有效,但毫无价值的语法在开关情况下?

由于一个小错别字,我偶然发现了这个结构:

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;
}

似乎switch语句顶部的printf是有效的,但也完全不可访问。

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

编译器应该将此标记为不可达代码吗?< br > 这有什么用吗?< / p >
14753 次浏览

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

-Wunreachable-code选项在GCC 4.4中被移除开始。

也许不是最有用的,但不是完全一文不值。你可以用它来声明一个在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的调用无法到达

顺便说一句,该示例不是有效的c++代码。在这种情况下(N4140 6.7/3,重点是我的):

一个程序将__abc0从具有自动存储持续时间的变量不在作用域内的点跳转到A 它在作用域中的点是格式错误的,除非变量有标量类型,类类型具有简单的默认值 构造函数和普通析构函数、这些类型之一的cv限定版本或类型之一的数组 并且声明时没有初始化式 (8.5).

. 0

90)从switch语句的条件到case标签的转换在这方面被认为是一个跳跃。

因此,用int i;替换int i = 4;使其成为一个有效的c++。

你得到了与required gcc选项 -Wswitch-unreachable相关的答案来生成警告,这个答案是关于可用性 / worthyness部分的详细说明。

直接引用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 . c 函数将访问一个不确定的值。类似地,调用 函数f无法到达

这是不言自明的。你可以使用它来定义一个局部作用域变量,该变量只能在switch语句作用域内可用。

这有什么用吗?

是的。如果你在第一个标签之前放一个声明而不是语句,这是完全有意义的:

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()调用是可达的。

不仅用于变量声明,还用于高级跳转。你可以很好地利用它,当且仅当你不倾向于面条代码。

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 *:标签在此代码中的位置,实际上没有任何结构限制*。这使得像达夫的设备这样的编程技巧成为可能,它的一个可能的实现如下所示:

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:标签之间的代码肯定是可达的…


* 正如supercat在评论中指出的那样:自C99以来,__ABC0和标签(不管是不是case *:标签)都不能出现在包含VLA声明的声明范围内。因此,说case *:标签的放置有no结构限制是不正确的。然而,达夫的设备早于C99标准,而且它并不依赖于VLA标准。然而,由于这个原因,我不得不在我的第一句话中插入一个“virtually”。

可以用它实现“循环半”,尽管这可能不是最好的方式:

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

有一个著名的用法叫做达夫的设备

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