C++ Force compile-time error/warning on implicit fall-through in switch

淺唱寂寞╮ 提交于 2019-11-28 18:17:56

问题


switch statements can be super useful, but lead to a common bug where a programmer forgot a break statement:

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

You won't get a warning obviously since sometimes fall-through is explicitly desired. Good coding style suggests to comment when your fall-through is deliberate, but sometimes that is insufficient.

I'm pretty sure the answer to this question is no, but: is there any way currently (or proposed in the future) to be able to ask the compiler to throw an error (or at least a warning!) if your case does not have at least one of break; or something to the effect of // fallthru? It would be nice to have a defensive programming option for using switch statements.


回答1:


Well clang has -Wimplicit-fallthrough which I did not know about but found by using -Weverything. So for this code it gives me the following warning (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; 

The only documentation I can find for this flag is in the Attribute Reference which says:

The clang::fallthrough attribute is used along with the -Wimplicit-fallthrough argument to annotate intentional fall-through between switch labels. It can only be applied to a null statement placed at a point of execution between any statement and the next switch label. It is common to mark these places with a specific comment, but this attribute is meant to replace comments with a more strict annotation, which can be checked by the compiler.

and provides an example of how to mark explicit fall-through:

case 44:  // warning: unannotated fall-through
g();
[[clang::fallthrough]];
case 55:  // no warning

This use of an attribute to mark explicit fall-through has the disadvantage of not being portable. Visual Studio generate an error and gcc generates the following warning:

warning: attributes at the beginning of statement are ignored [-Wattributes]

which is a problem if you want to use -Werror.

I tried this with gcc 4.9 and it looks like gcc does not support this warning:

error: unrecognized command line option '-Wimplicit-fallthrough'

As of GCC 7, -Wimplicit-fallthrough is supported and __attribute__((fallthrough)) can be used to suppress the warnings when fallthrough is intentional. GCC does recognize "fallthrough" comments in certain scenarios, but it can be confused fairly easily.

I do not see a way of generating such a warning for Visual Studio.

Note, Chandler Carruth explains that -Weverything is not for production use:

This is an insane group that literally enables every warning in Clang. Don't use this on your code. It is intended strictly for Clang developers or for exploring what warnings exist.

but it is useful for figuring out what warnings exist.

C++17 changes

In C++17 we get the attribute [[fallthrough]] covered in [dcl.attr.fallthrough]p1:

The attribute-token fallthrough may be applied to a null statement (9.2); such a statement is a fallthrough statement. The attribute-token fallthrough shall appear at most once in each attribute-list and no attributeargument- clause shall be present. A fallthrough statement may only appear within an enclosing switch statement (9.4.2). The next statement that would be executed after a fallthrough statement shall be a labeled statement whose label is a case label or default label for the same switch statement. The program is ill-formed if there is no such statement.

...

[ 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 ]

See live example using attribute.




回答2:


I always write a break; before each case, as follows:

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

This way, it is much more obvious to the eye if a break; is missing. The initial break; is redundant I suppose, but it helps to be consistent.

This is a conventional switch statement, I've simply used whitespace in a different way, removing the newline that is normally after a break; and before the next case.




回答3:


Advice: if you consistently put a blank line in between case clauses, the absence of a 'break' becomes more visible to a human skimming the code:

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

    case 1:
        bar();

    case 2:
        baz();
        break;

    default:
        roomba();
}

This isn't as effective when there's a lot of code inside individual case clauses, but that tends to be a bad code smell in itself.




回答4:


Here's an answer to compulsively hate.

First, switch statements are fancy gotos. They can be combined with other control flow (famously, Duff's Device), but the obvious analogy here is a goto or two. Here's a useless example:

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:

Do I recommend this? No. In fact, if you're considering this just to annotate a fall-through, then your problem is actually something else.

This sort of code does make it very clear that a fall-through can happen in case 1, but as the other bits show, this is a very powerful technique in general that also lends itself to abuse. Be careful if you use it.

Forgetting a break? Well, then you'll also occasionally forget whatever annotation you pick. Forgetting to account for fall-through when changing a switch statement? You're a bad programmer. When modifying switch statements(or really any code), you need to first understand them.


Honestly, I very seldom make this kind of error (forgetting a break)--certainly less than I made other "common" programming errors (strict aliasing, for example). To be safe, I currently do (and recommend you to do) just write //fallthrough, since this at least clarifies intention.

Other than that, it's a just a reality that programmers need to accept. Proofread your code after you write it and find the occasional problem with debugging. That's life.



来源:https://stackoverflow.com/questions/27965722/c-force-compile-time-error-warning-on-implicit-fall-through-in-switch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!