C ++强制编译时错误/警告在交换机中隐式掉落

时间:2015-01-15 14:32:05

标签: c++ switch-statement compiler-warnings

switch语句可能非常有用,但会导致程序员忘记中断语句的常见错误:

switch(val) {
    case 0:
        foo();
        break;
    case 1:
        bar();
        // oops
    case 2:
        baz();
        break;
    default:
        roomba();
}

你很明显不会收到警告,因为有时明确需要通过。良好的编码风格建议在你的堕落是故意的时候发表评论,但有时这是不够的。

我很确定这个问题的答案是否定的,但是:目前(或将来提出的)是否能够让编译器抛出错误(或者至少是警告!)您的case至少没有break;中的一个或// fallthru的效果?使用switch语句有一个防御性编程选项会很好。

5 个答案:

答案 0 :(得分:67)

铿锵有-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属性与。一起使用    - 用于注释切换标签之间故意掉落的轻微 - 通过秋季参数。它只能应用于null语句   放在任何语句和下一个语句之间的执行点   开关标签。通常用特定的标记来标记这些地方   注释,但此属性旨在用更多注释替换注释   严格注释,可由编译器检查。

并提供了如何标记显式直通的示例:

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))Visual Studio可用于在故意触发故障时抑制警告。海湾合作委员会确实在某些情况下承认“通过”评论,但可能会混淆fairly easily

我没有看到为-Weverything生成此类警告的方法。

注意,Chandler Carruth说明[ 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 ] 不适合生产使用:

  

这是一个疯狂的团体,从字面上实现了Clang的每一个警告。   不要在代码中使用它。它主要用于Clang   开发人员或探索存在的警告。

但它有助于弄清楚存在什么警告。

C ++ 17更改

在C ++ 17中,我们得到[dcl.attr.fallthrough]p1中涵盖的属性 [[fallthrough]]

  

属性标记可以应用于null语句(9.2);这样的声明是一个突破   声明。属性 - 标记通过在每个属性列表中最多出现一次,并且没有属性参数 -   条款应在场。 fallthrough语句可能只出现在封闭的开关中   声明(9.4.2)。在fallthrough语句之后执行的下一个语句应为a   带标签的语句,其标签是同一switch语句的case标签或default标签。该计划是   如果没有这样的陈述,就会形成错误。

     

...

{{1}}

请参阅live example using attribute

答案 1 :(得分:12)

我总是在每个break;之前写一个case,如下所示:

switch(val) {
    break; case 0:
        foo();
    break; case 1:
        bar();
    break; case 2:
        baz();
    break; default:
        roomba();
}

这样,如果缺少break;,则更明显。我认为最初的break;是多余的,但它有助于保持一致。

这是一个传统的switch语句,我只是以不同的方式使用空格,删除通常在break;之后和下一个case之前的换行符。

答案 2 :(得分:6)

建议:如果你一直在case case子句之间添加一个空行,那么人们在查看代码时就会看到没有“break”:

switch (val) {
    case 0:
        foo();
        break;

    case 1:
        bar();

    case 2:
        baz();
        break;

    default:
        roomba();
}

当个别case子句中有很多代码时,这并不是那么有效,但这本身往往是一个糟糕的代码味道。

答案 3 :(得分:1)

这是强迫性仇恨的答案。

首先,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,因为这至少说明了意图。

除此之外,它是程序员需要接受的一个现实。在编写代码后校对代码并发现调试时偶尔出现的问题。这就是生命。

答案 4 :(得分:0)

您可以使用“python 样式”开关,如下所示(see it live,也许令人惊讶的是,这是 实际上符合标准的合法结构)。

关键技巧是让整个 switch 语句变得简单 声明 - 即像往常一样有作用域块。

相反,我们开始一个链式 if 语句,其中散布着 case 标签。 if 语句本身无法访问,但您可以使用 else if(/*irrelevant*/) 链接以永不失败的方式继续切换。

else 很好地排除了其他分支。实际上它是一个“开关树” 现在,带有绕过条件的标签。

switch (a) if (false)
    case 4:
    case 5:
    case 6:
        std::cout << "4,5,6" << std::endl; //single statement
        else if (false)
    case 7:
    case 8:
        { //compound is ok too
            std::cout << "7,";
            std::cout << "8" << std::endl;
        }
        else if (false)
    default:
        std::cout << "default" << std::endl;

注意:

  • 实际条件表达式未使用(案例标签跳过 条件)

  • 你实际上可以使用像

    这样的宏
      #define FORCEBREAK else if(false)
    

    突出意思。

缺点:

  • 最后一个方面突出了一个缺陷:严格来说它需要更多的工作,而 C++17 [[fallthrough]] 大多只是更好
  • 您现在需要消除编译器给出的无法访问的代码警告 (例如 GCC 要求 -Wno-switch-unreachable 在没有警告的情况下接受它)

优点:

  • 太棒了
  • 它介绍了 switch 语句的灵活性
  • 确实迫使人们正确地打破他们的 switch case,否则它会因编译时错误而失败
  • 它也适用于 C with minor modifications

Live On Compiler Explorer

#include <iostream>

void switchless(int a) {
    switch (a) if (false)
        case 4:
        case 5:
        case 6:
            std::cout << "4,5,6" << std::endl; //single statement
            else if (false)
        case 7:
        case 8:
            { //compound is ok too
                std::cout << "7,";
                std::cout << "8" << std::endl;
            }
            else if (false)
        default:
            std::cout << "default" << std::endl;
}

int main() {
    for (int i = 0; i < 10; ++i)
        switchless(i);
}

印刷品

default
default
default
default
4,5,6
4,5,6
4,5,6
7,8
7,8
default