switch
语句可能非常有用,但会导致程序员忘记中断语句的常见错误:
switch(val) {
case 0:
foo();
break;
case 1:
bar();
// oops
case 2:
baz();
break;
default:
roomba();
}
你不会明显得到警告,因为有时明确需要通过。良好的编码风格建议在你的堕落是故意的时候发表评论,但有时这是不够的。
我很确定这个问题的答案是否定的,但是:如果你的case
确实能够要求编译器抛出一个错误(或者至少是一个警告!)没有至少一个break;
或// fallthru
效果?使用switch
语句有一个防御性编程选项会很好。
铿锵有-Wimplicit-fallthrough
,我不知道,但发现使用-Weverything
。所以对于这段代码,它给了我以下警告(see it live):
warning: unannotated fall-through between switch labels [-Wimplicit-fallthrough]
case 2:
^
note: insert '[[clang::fallthrough]];' to silence this warning
case 2:
^
[[clang::fallthrough]];
note: insert 'break;' to avoid fall-through
case 2:
^
break;
我可以找到这个标志的唯一文件是在Attribute Reference中说:
clang :: fallthrough属性与-Wimplicit-fallthrough参数一起使用,用于注释switch标签之间的故意直通。它只能应用于放置在任何语句和下一个开关标签之间的执行点的空语句。通常使用特定注释标记这些位置,但此属性旨在用更严格的注释替换注释,编译器可以检查注释。
并提供了如何标记显式掉落的示例:
case 44: // warning: unannotated fall-through
g();
[[clang::fallthrough]];
case 55: // no warning
使用attribute来标记明显的穿透性具有不可移植的缺点。 Visual Studio
生成错误,gcc
生成以下警告:
warning: attributes at the beginning of statement are ignored [-Wattributes]
如果你想使用-Werror
,这是一个问题。
我用gcc 4.9
尝试了这个,看起来gcc
不支持这个警告:
错误:无法识别的命令行选项'-Wimplicit-fallthrough'
从GCC 7开始,-Wimplicit-fallthrough
得到支持,并且__attribute__((fallthrough))
可用于在有意进行击穿时抑制警告。海湾合作委员会确实在某些情况下承认“通过”评论,但它可能会混淆fairly easily。
我没有看到为Visual Studio
产生这种警告的方法。
请注意,Chandler Carruth解释说-Weverything
不用于生产用途:
这是一个疯狂的团体,从字面上实现了Clang的每一个警告。不要在代码中使用它。它仅供Clang开发人员使用,或用于探索存在的警告。
但它有助于弄清楚存在什么警告。
在C ++ 17中,我们得到了[dcl.attr.fallthrough]p1中涵盖的属性[[fallthrough]]:
属性标记可以应用于null语句(9.2);这样的陈述是一个突破性的陈述。属性 - 标记通过在每个属性列表中最多出现一次,并且不存在attributeargument-子句。 fallthrough语句只能出现在封闭的switch语句中(9.4.2)。在fallthrough语句之后执行的下一个语句应该是带标签的语句,其标签是同一switch语句的case标签或default标签。如果没有这样的声明,该计划就是不正确的。
...
[ Example: void f(int n) { void g(), h(), i(); switch (n) { case 1: case 2: g(); [[fallthrough]]; case 3: // warning on fallthrough discouraged h(); case 4: // implementation may warn on fallthrough i(); [[fallthrough]]; // ill-formed } } —end example ]
我总是在每个break;
之前写一个case
,如下:
switch(val) {
break; case 0:
foo();
break; case 1:
bar();
break; case 2:
baz();
break; default:
roomba();
}
这样,如果缺少break;
,那么眼睛会更加明显。我认为最初的break;
是多余的,但它有助于保持一致。
这是一个传统的switch
语句,我只是以不同的方式使用空格,删除通常在break;
之后和下一个case
之前的换行符。
建议:如果你一直在case子句之间加上一个空行,那么人们在查看代码时就会看到没有'break'变得更加明显:
switch (val) {
case 0:
foo();
break;
case 1:
bar();
case 2:
baz();
break;
default:
roomba();
}
当个别case子句中有很多代码时,这并不是那么有效,但这本身往往是一个糟糕的代码味道。
这是强迫性仇恨的答案。
首先,switch
的陈述是花哨的。它们可以与其他控制流程(着名的Duff's Device)结合使用,但这里显而易见的类比是goto或者两个。这是一个无用的例子:
switch (var) {
CASE1: case 1:
if (foo) goto END; //same as break
goto CASE2; //same as fallthrough
CASE2: case 2:
break;
CASE3: case 3:
goto CASE2; //fall *up*
CASE4: case 4:
return; //no break, but also no fallthrough!
DEFAULT: default:
continue; //similar, if you're in a loop
}
END:
我推荐这个吗?不。实际上,如果你考虑这只是为了注释一个堕落,那么你的问题实际上就是其他问题。
这种代码确实很清楚,在案例1中可能会发生漏洞,但正如其他位所示,这是一种非常强大的技术,通常也会导致滥用。如果使用它,请小心。
忘了break
?好吧,那么你偶尔会忘记你选择的任何注释。在更改switch语句时忘记考虑掉落?你是一个糟糕的程序员。在修改switch语句(或任何代码)时,您需要先了解它们。
老实说,我很少犯这种错误(忘记休息) - 肯定比我做其他“常见”编程错误(例如严格别名)少。为了安全起见,我现在(并建议你这样做)只写//fallthrough
,因为这至少说明了意图。
除此之外,这是程序员需要接受的一个现实。在编写代码后校对代码并发现调试时偶尔出现的问题。这就是生活。