考虑下面的switch语句:
switch( value )
{
case 1:
return 1;
default:
value++;
// fall-through
case 2:
return value * 2;
}
此代码编译,但它是有效的(=定义的行为)C90/C99?我从未见过默认情况不是最后一个情况的代码。
编辑:
正如Jon Cage和KillianDS所写的:这真的是丑陋而令人困惑的代码,我很清楚这一点。我只对通用语法(有定义吗?)和预期的输出感兴趣。
在某些情况下,它是有效的,非常有用的。
考虑下面的代码:
switch(poll(fds, 1, 1000000)){
default:
// here goes the normal case : some events occured
break;
case 0:
// here goes the timeout case
break;
case -1:
// some error occurred, you have to check errno
}
重点是上面的代码比级联的if更可读,更有效。你可以把default放在最后,但这是毫无意义的,因为它会把你的注意力集中在错误情况上,而不是正常情况上(这里是默认情况)。
实际上,这不是一个很好的例子,在民意调查中你知道最多可能发生多少事件。我真正的观点是,在某些情况下,有一组定义好的输入值,其中有“例外”和正常情况。将异常或正常情况放在前面更好,这是一个选择的问题。
在软件领域,我想到了另一种非常常见的情况:带有一些终端值的递归。如果可以使用开关表示它,默认值将是包含递归调用的常用值和终端值的不同元素(个别情况)。通常不需要关注终端值。
Another reason is that the order of the cases may change the compiled code behavior, and that matters for performances. Most compilers will generate compiled assembly code in the same order as the code appears in the switch. That makes the first case very different from the others: all cases except the first one will involve a jump and that will empty processor pipelines. You may understand it like branch predictor defaulting to running the first appearing case in the switch. If a case if much more common that the others then you have very good reasons to put it as the first case.
阅读评论,这就是为什么最初的海报在阅读英特尔编译器分支循环重组关于代码优化后提出这个问题的具体原因。
然后,它将成为代码可读性和代码性能之间的仲裁。也许最好是写个评论,向将来的读者解释为什么先出现一个案例。
C99标准没有明确说明这一点,但综合所有事实来看,它是完全有效的。
case和default标签等同于goto标签。参见6.8.1标记语句。特别有趣的是6.8.1.4,它启用了前面提到的达夫装置:
任何语句前都可以加上
声明标识符为的前缀
标签名称。标签本身
不改变流控制,其中
继续畅通无阻地穿过它们。
编辑:开关内的代码没有什么特别的;它是一个正常的if语句代码块,带有额外的跳转标签。这解释了跌倒行为,以及为什么休息是必要的。
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的调用
无法联系上。
case常量在switch语句中必须是唯一的:
6.8.4.2.3各大小写标号的表达式为整数常数
表达和没有两种的情况
常数表达式是一样的
开关语句也应相同
转换后的值。可能有
一个交换机最多只能有一个默认标签
声明。
所有的情况都被评估,然后它跳转到默认标签,如果给定:
6.8.4.2.5 The integer promotions are performed on the controlling
expression. The constant expression in
each case label is converted to the
promoted type of the controlling
expression. If a converted value
matches that of the promoted
controlling expression, control jumps
to the statement following the matched
case label. Otherwise, if there is a
default label, control jumps to the
labeled statement. If no converted
case constant expression matches and
there is no default label, no part of
the switch body is executed.
switch语句中没有定义的顺序。你可以把这些情况看作一个命名标签,一个goto标签。与人们在这里的想法相反,在值2的情况下,默认标签不会跳转到。为了用一个经典的例子来说明,这里是Duff的设备,它是C语言中switch/case的极端代表。
send(to, from, count)
register short *to, *from;
register count;
{
register n=(count+7)/8;
switch(count%8){
case 0: do{ *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
}while(--n>0);
}
}
在一种情况下,我认为将默认情况设置在switch语句的末尾以外的地方是合适的,即在状态机中,无效状态应该重置机器并继续执行,就像它是初始状态一样。例如:
switch(widget_state)
{
default: /* Fell off the rails--reset and continue */
widget_state = WIDGET_START;
/* Fall through */
case WIDGET_START:
...
break;
case WIDGET_WHATEVER:
...
break;
}
另一种安排,如果无效状态不应该重置机器,但应该容易识别为无效状态:
switch(widget_state)
{
case WIDGET_IDLE:
widget_ready = 0;
widget_hardware_off();
break;
case WIDGET_START:
...
break;
case WIDGET_WHATEVER:
...
break;
default:
widget_state = WIDGET_INVALID_STATE;
/* Fall through */
case WIDGET_INVALID_STATE:
widget_ready = 0;
widget_hardware_off();
... do whatever else is necessary to establish a "safe" condition
}
其他地方的代码可以检查widget_state == WIDGET_INVALID_STATE,并提供任何合适的错误报告或状态重置行为。例如,状态条形码可以显示一个错误图标,在大多数非空闲状态下禁用的“启动小部件”菜单选项可以为WIDGET_INVALID_STATE和WIDGET_IDLE启用。
默认条件可以是开关中case子句可以存在的任何位置。它不需要是最后一个子句。我曾见过将默认值作为第一个子句的代码。情况2:正常执行,即使默认子句在它上面。
作为测试,我把示例代码放在一个名为test(int value){}的函数中,并运行:
printf("0=%d\n", test(0));
printf("1=%d\n", test(1));
printf("2=%d\n", test(2));
printf("3=%d\n", test(3));
printf("4=%d\n", test(4));
输出结果为:
0=2
1=1
2=4
3=8
4=10