59

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.

smci
  • 32,567
  • 20
  • 113
  • 146
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Which compiler are you using? I know I've seen [discussion](http://stackoverflow.com/questions/7703358/how-can-i-tell-gcc-to-warn-or-fail-on-switch-case-statements-without-a-break) of adding such an optional warning to gcc. Not sure what's come of that yet though. – Cory Kramer Jan 15 '15 at 14:34
  • sounds like a change that could potentially break legacy code if it is anything other than a warning. – AndersK Jan 15 '15 at 14:35
  • @Cyber I'm using gcc 4.7 – Barry Jan 15 '15 at 14:36
  • Also it seems that some static code analyzers like CPPCheck will catch this if you use that. – Cory Kramer Jan 15 '15 at 14:36
  • @Cyber Actually, cppcheck did not find the specific missing break that led to this question :( – Barry Jan 15 '15 at 14:40
  • If you use polymorphism instead of switching you'll always avoid this stuff! :D – Яois Jan 15 '15 at 14:47
  • 1
    There was a [coverity blog post](http://security.coverity.com/blog/2013/Sep/gimme-a-break.html) about this very topic a few months ago. – fredoverflow Jan 15 '15 at 15:58
  • 1
    @KeillRandor - Another way to "avoid all this stuff" is to use a function table. – David Hammen Jan 16 '15 at 09:21

5 Answers5

76

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.

isanae
  • 3,253
  • 1
  • 22
  • 47
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 2
    This is why `-Weverything` (which the GCC folks are apparently loathe to emulate) is so useful. No, devs will probably not use it in the "official" set of warnings used to compile their production code, but it's extremely helpful to run codebases against it once in a while to see if there are any genuinely helpful warnings you're missing. – Kyle Strand Sep 14 '15 at 16:37
  • @KyleStrand I agree, I have found a few useful warnings this way. – Shafik Yaghmour Sep 14 '15 at 16:41
  • 8
    [[fallthrough]] is now an [official C++17 annotation](http://en.cppreference.com/w/cpp/language/attributes) so hopefully more compilers will add support for optional warnings when statements fall through without this annotation. – Chris Kline May 11 '16 at 12:40
  • Note: there There is an open request for [[fallthrough]] and a related compiler warning to be implemented for Visual C++; [you can vote for it if you would like to see this as well](https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/13806144-implement-the-c-17-fallthrough-annotation-and). – Chris Kline May 11 '16 at 12:54
  • Worth noting `-Wimplicit-fallthrough` is not implement [for C in clang but is in gcc](https://twitter.com/shafikyaghmour/status/9618193954660597770) – Shafik Yaghmour Feb 09 '18 at 04:32
  • C26819 appears to be what you want in visual studio, but I cannot get this to work. Perhaps related to this comment: https://stackoverflow.com/questions/21909857/how-can-i-make-msvc-warn-or-fail-if-a-switch-case-falls-through/65925560#comment123102664_65925560 – jtooker Mar 16 '23 at 14:38
13

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.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • 11
    How about `#define CASE break; case`? ;) – fredoverflow Jan 15 '15 at 15:54
  • 6
    @FredOverflow: That would defeat the purpose of making the `break;`s more visually prominent. – Nick Matteo Jan 15 '15 at 17:35
  • 3
    What do you do in the fallthrough case? Just leave out the `break`? – nwp Jan 15 '15 at 18:35
  • I don't recall having to allow any fallthrough cases recently. I guess I would just leave it out, perhaps with a comment too. Something that stands out. – Aaron McDaid Jan 15 '15 at 19:24
  • I never knew writing `break` before the first `case` works. And this doesn't cause any issues? – David says Reinstate Monica Jan 15 '15 at 20:12
  • 1
    @DavidGrinberg it works because the switch statement functions with labels: the compiler breaks down each command separated by semi-colons and jumps to the one labeled with the value of the variable being switched. If the code is compilable you can put it before the first case, it will just never be reached. You can see that behaviour in this example program I just wrote, the assembly code generated only reaches after the case: http://goo.gl/XocrS3 – Lucas C. Feijo Jan 15 '15 at 21:06
  • 4
    @FredOverflow I find defines that change native commands like that a bit intrusive. – Lucas C. Feijo Jan 15 '15 at 21:08
  • 1
    "That would defeat the purpose of making the break;s more visually prominent." -- There is no such purpose. – Jim Balter Jan 15 '15 at 23:06
  • 21
    Wow, that's horrible to look at – James Jan 16 '15 at 09:02
  • @DavidGrinberg, this approach does not change the order of any way - I just changes whitespaces. Because C ignores whitespace, a `break` before a `case` is just the same as having a `break` at the end of the previous set of statements. I do introduce a `break` at the very start, before the first `case`, but that changes nothing. In summary, this is a conventional `switch` statement, but with more intelligent use of whitespace – Aaron McDaid Jan 16 '15 at 09:42
6

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.

zwol
  • 135,547
  • 38
  • 252
  • 361
0

You can use a "python style" switch as follows (see it live, perhaps to one's surprise this is actually legal construct accoriding to the standard).

The key trick is to make the entire switch statement a simple statement - i.e. there is scope block, as usual.

Instead, we start a chaining if-statement interspersed with case labels. The if statement itself is unreachable, but you can chain with else if(/*irrelevant*/) to continue the switch in a way that never falls through.

The else nicely excludes the other branches. In effect it's a "switch tree" now, with labels that bypass the conditions.

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;

Note that:

  • the actual condition expression is unused (the case labels jump past the condition)

  • you could actually use a macro like

      #define FORCEBREAK else if(false)
    

    to highlight the meaning.

Cons:

  • That last aspect kind of highlights a flaw: it's strictly more work, and C++17 [[fallthrough]] is mostly just better
  • You now need to silence the unreachable code warning that compilers give (e.g. GCC requires -Wno-switch-unreachable to accept it without warning)

Pros:

  • It's fancy
  • It teaches about the flexibilities of switch statements
  • It does force people to correctly break their switch cases, or it will fail with compile-time errors
  • It applies to C as well 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);
}

Prints

default
default
default
default
4,5,6
4,5,6
4,5,6
7,8
7,8
default
sehe
  • 374,641
  • 47
  • 450
  • 633
AnArrayOfFunctions
  • 3,452
  • 2
  • 29
  • 66
  • 1
    Yeah, but now you can't use standard warning flags https://godbolt.org/z/TKvT7rYr1. Also, what does this add? If you are disciplined enough to add **two** magic tokens (`if (true)` **and** `else`) then surely you will be motivated to add one token? (`break;`) – sehe Mar 23 '21 at 17:00
  • @sehe You can't add a new case without the proper `else case n: if(true)` and **not** get a compile time error. With `break` you can easily miss it. – AnArrayOfFunctions Mar 23 '21 at 17:02
  • This is substantially worse than using `[[fallthrough]]`. – Barry Mar 23 '21 at 17:59
  • 1
    Mmm. I thought that you could add one at the front. However, I missed (because you didn't hightlight/explain it) that there wasn't a `{}` block for the switch itself. Given that, I would say these two changes make it **striclty** better: https://godbolt.org/z/hjaqasoYM - note that this is basically ***exactly*** what you get if you replase `break` with `else if (false)`. [And notice I had to silence another warning that is even on by default.] – sehe Mar 23 '21 at 18:29
  • @Barry In their defense, it's an improvement on the cursed code from [this 2015 answer](https://stackoverflow.com/a/27972986/85371). If they improve the explanation to emphasize the absence of a switch block (so the entire body is a "single statement") I'd give it a +1 here. – sehe Mar 23 '21 at 18:29
  • @sehe Edited to reflect that as best as possible. – AnArrayOfFunctions Mar 23 '21 at 18:38
  • 1
    @sehe Well, yes, that answer is certainly worse than this one. But the problem here has a _very_ good solution in `[[fallthrough]]` (and the answer also includes other historic approaches that predate that attribute) and there is really no reason to suggest any other approach. – Barry Mar 23 '21 at 18:53
  • @Barry For a C user there might be. I don't think `[[fallthrough]]` is available to C plus it's just a warning - my example will produce an error. – AnArrayOfFunctions Mar 23 '21 at 18:55
  • @AnArrayOfFunctions the question is tagged [tag:c++] – sehe Mar 23 '21 at 18:56
  • 1
    gcc and clang both also support `__attribute__((fallthrough))` in C: [demo](https://godbolt.org/z/Eh5PYcYr8) – Barry Mar 23 '21 at 19:02
  • @Barry Good point but you can't deny my answers is fancy. I just wanted to share cause I found it amuzing myself too. – AnArrayOfFunctions Mar 23 '21 at 19:04
  • @AnArrayOfFunctions `-Werror` is your friend. Or use [`#pragma` to make the specific warning an error](https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html) – sehe Mar 23 '21 at 19:04
  • @AnArrayOfFunctions Sure he can deny that. With good reason. It's about as "fancy" as K&R-style function prototypes. Your defition of fancy doesn't need to match that of others. – sehe Mar 23 '21 at 19:05
  • And it's a solution. We have two. What's wrong with that? – AnArrayOfFunctions Mar 23 '21 at 19:05
  • 1
    Fixed it some more; note I flipped the order so it's much clearer that the entire body is an if-statement, the conditions don't matter (and are elimintaed as dead code) and, last but not least, it works well with formatting tools (like clang-format) – sehe Mar 23 '21 at 19:33
  • @AnArrayOfFunctions The reasons to downvote are in the help for the site. The tooltip text says "This answer is not useful". I think that's a very valid opinion in 2021. I like the construct - I listed "fancy" under the "Pros" for you in the edits. But I won't ever use this, because there's no need for it :) – sehe Mar 23 '21 at 19:35
-1

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
geometrian
  • 14,775
  • 10
  • 56
  • 132
  • 5
    Do you also compile with all warnings disabled because you consider proofreading sufficient? – Barry Jan 15 '15 at 21:26
  • 10
    I am compulsively hating this. – matsjoyce Jan 15 '15 at 21:27
  • @Barry Proofreading _was_ sufficient. It [compiles just fine](http://ideone.com/1EtHwa) and does what it should. I assume you mean the unreferenced labels, which I included deliberately for clarity. I'm not sorry about them. – geometrian Jan 15 '15 at 22:11
  • 2
    Interesting excercise. – Chris Tonkinson Jan 20 '15 at 22:26
  • couldn't resist to use my down vote rights (first time)... this doesn't improve readability to spot the missing break.. and it doesn't do anything to improve the logic.. the whitespace option seemed best to spot missing break statements. – Heston T. Holtmann Apr 05 '17 at 17:40
  • 1
    This is just a rant. – S.S. Anne Aug 07 '19 at 16:42
  • I think it is an answer. What's more, it does seem to be an answer to the question. [From Review](https://stackoverflow.com/review/low-quality-posts/23736783). – Wai Ha Lee Aug 07 '19 at 16:46