有效的,但没有价值的语法在开关柜?
通过一个小小的错字,我意外地发现了这个结构:
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
是有效的,但也是完全无法访问的。
我得到了一个干净的编译,甚至没有关于无法访问的代码的警告,但这似乎毫无意义。
一个编译器应该将这个标记为不可达代码?
这是否有任何用途?
也许不是最有用的,但不是完全没有价值的。 您可以使用它来声明switch
范围内可用的本地variables。
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
的对象存在自动存储持续时间(在块内),但是从不初始化,因此如果控制expression式具有非零值,则对printf
函数的调用将访问不确定的值。 类似地,无法到达函数f
的调用。
PS顺便说一句,该示例是无效的C ++代码。 在这种情况下( N4140 6.7/3
,重点矿):
从自动存储持续时间的variables不在范围内的点跳转90的程序是不合格的, 除非variables具有标量types ,具有简单的默认构造函数和普通的析构函数的类types,这些types之一的cv限定版本,或前面types之一的数组, 并且声明时没有初始值设定项 (8.5)。
90)从
switch
语句转换为case标签在这方面被认为是一个跳跃。
所以取代int i = 4;
与int i;
使其成为一个有效的C ++。
这是否有任何用途?
是。 如果不是一个声明,你在第一个标签之前放置一个声明,这可以是完全合理的:
switch (a) { int i; case 0: i = f(); g(); h(i); break; case 1: i = g(); f(); h(i); break; }
声明和声明的规则通常是为块共享的,所以它是相同的规则,允许那里也允许声明。
值得一提的是,如果第一个语句是一个循环结构,则循环体中可能会出现个案标签:
switch (i) { for (;;) { f(); case 1: g(); case 2: if (h()) break; } }
如果有更可读的写法,请不要写这样的代码,但它是完全有效的,并且f()
调用是可访问的。
有一个着名的使用这个叫达夫的设备 。
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); }
这里我们复制一个指向的缓冲区到to
指向的缓冲区。 我们复制数据的实例。
do{}while()
语句在第一个case
标签之前开始, case
标签embeddeddo{}while()
。
这会减lessdo{}while()
循环结束时的条件分支的数量,大致乘以4(在这个例子中,常量可以调整到任何你想要的值)。
现在,优化器有时可以为你做(特别是如果他们正在优化stream化/vector化指令),但没有configuration文件指导的优化,他们不知道你是否期望循环大或不。
一般情况下,variables声明可以在这里出现并在任何情况下使用,但是在切换结束之后超出范围。 (注意任何初始化都会被跳过)
另外,不是开关特定的控制stream程可以让你进入开关块的那个部分,如上所述,或者goto
。
假设你在Linux上使用gcc,如果你使用的是4.4或更早的版本,它会给你一个警告。
在gcc 4.4以后,-Wunreachable-code选项被移除了 。
不仅适用于variables声明,也适用于高级跳转。 当且仅当你不喜欢意大利式面条代码时,你可以很好地利用它。
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是最快的控制stream程条款之一。 所以程序员必须非常灵活,有时会涉及到这样的情况。
应该注意的是, switch
语句中的代码几乎没有结构上的限制,或者case *:
标签放在这个代码中*的地方。 这使得像duff设备这样的编程技巧成为可能,其中一个可能的实现如下所示:
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以来,在包含VLA声明的声明的范围内,可能不会出现goto
或label(不论是否为case *:
label或label)。 所以说这个case *:
的放置没有结构限制是不正确的case *:
标签。 但是,duff的设备早于C99标准,并且不依赖于VLA。 不过,由于这个原因,我觉得不得不在第一句话中插入一个“虚拟的”。
你得到了你的答案有关的所需的gcc
选项 -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
的对象存在自动存储持续时间(在块内),但是从不初始化 ,因此如果控制expression式具有非零值,则对printf
函数的调用将访问不确定的值。 类似地,无法达到函数f
的调用。
这是非常明确的。 您可以使用它来定义一个仅在switch
语句范围内可用的本地作用域variables。
用它来实现一个“循环半”是可能的,尽pipe它可能不是最好的方法:
char password[100]; switch(0) do { printf("Invalid password, try again.\n"); default: read_password(password, sizeof(password)); } while (!is_valid_password(password));