16

Reviewing some 3rd party C code I came across something like:

switch (state) {
case 0: 
    if (c=='A') { // open brace
        // code...
    break; // brace not closed!
case 1:
    // code...
    break;
    } // close brace!
case 2:
    // code...
    break;
}

Which in the code I was reviewing appeared to be just a typo but I was surprised that it compiled with out error.

Why is this valid C?
What is the effect on the execution of this code compared to closing the brace at the expected place?
Is there any case where this could be of use?

Edit: In the example I looked at all breaks were present (as above) - but answer could also include behaviour if break absent in case 0 or 1.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Ricibob
  • 7,505
  • 5
  • 46
  • 65
  • Well, that's because of `switch-case`'s odd `goto label` implementation. While your particular case is probably a bit weird and hard to come up with a use case for (look at [*Duff's Device*](http://en.wikipedia.org/wiki/Duff%27s_device), though), the general concept of fall-through cases (when you leave away the `break`) can indeed be quite useful. – Christian Rau Jun 19 '13 at 13:21
  • 2
    In case you are now seeking an explanation on how *Duff's device* works, [here](http://stackoverflow.com/questions/514118/how-does-duffs-device-work) are some. – devnull Jun 19 '13 at 13:26
  • In this case `case 1:` is being treated by the compiler as a seperate label. The syntax is perfectly valid, but almost certainly (from the context) a logic error in this situation. Test it by sending `state ==1` through and you will see the incorrect result. – Chad Jun 19 '13 at 13:28

2 Answers2

14

Not only is it valid, similar structure has been used in real code, e.g., Duff's Device, which is an unrolled loop for copying a buffer:

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);
        }
}

Since a switch statement really just computes an address and jumps to it, it's easy to see why it can overlap with other control structures; the lines within other control structures have addresses that can be jump targets, too!

In the case you presented, imagine if there were no switch or breaks in your code. When you've finished executing the then portion of a if statement, you just keep going, so you'd fall through into the case 2:. Now, since you have the switch and break, it matters what break can break out of. According to the MSDN page, “The C break statement”,

The break statement terminates the execution of the nearest enclosing do, for, switch, or while statement in which it appears. Control passes to the statement that follows the terminated statement.

Since the nearest enclosing do, for, switch, or while statement is your switch (notice that if is not included in that list), then if you're inside the then block, you transfer to the outside of the switch statement. What's a bit more interesting, though, is what happens if you enter case 0, but c == 'A' is false. Then the if transfers control to just after the closing brace of the then block, and you start executing the code in case 2.

Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • 2
    The c!='A' case makes sense the way you explain it - but that is FAR from intuitive from a quick glance at the code! – Ricibob Jun 19 '13 at 13:37
  • 1
    @Ricibob It does take a certain amount of viewing C as “high level assembly,” I admit. If you've spent some time looking at how C code is compiled down to assembly/machine code, it gets a bit easier. For instance, in an `if condition then` statement, there's only one jump; if the condition is _false_, jump to just after the `then` portion. If you end up in the `then` portion, continuing execution will get you there too. There's no need for a `break`. In an iteration form, however, e.g., `while condition block`, the block gets compiled to have a jump back to the beginning to test the… – Joshua Taylor Jun 19 '13 at 13:43
  • 1
    @Ricibob …condition again, and either go into the block again, or jump to after the block. So iteration constructs need `break` because the body would bring them back to the beginning. `switch` is sort of an odd one though, since it doesn't do iteration. It just needs `break` because it's very common that programmers want to execute just one case and not fall through the rest, and without break they'd have to write a new label just after the switch statement and `goto` it from each case. In any case, it's probably not good style to do this sort of control flow interleaving unless you've… – Joshua Taylor Jun 19 '13 at 13:46
  • 1
    @Ricibob …got a very good reason, so you are unlikely to see it in the wild, and you should be even more unlikely to write it. – Joshua Taylor Jun 19 '13 at 13:46
  • Thanks for the explainations. Switch is clearly still waters run deep - it looks straight forward on the surface but potentially lots going on under the hood. Makes me appreciate the choices made to simplify switch in C#. Im making a mental not to NEVER use this particualar switch construct... – Ricibob Jun 19 '13 at 13:52
3

In C and C++ it is legal to jump into loops and if blocks so long as you don't jump over any variable declarations. You can check this answer for an example using goto, but I don't see why the same ideas wouldn't apply to switch blocks.

The semantics are different than if the } was above case 1 as you would expect.
This code actually says if state == 0 and c != 'A' then go to case 2 since that's where the closing brace of the if statement is. It then processes that code and hits the break statement at the end of the case 2 code.

Community
  • 1
  • 1
SirGuy
  • 10,660
  • 2
  • 36
  • 66
  • I think you _can_ jump into blocks containing variables as long you don't jump from the outside to the inside of the scope of an identifier with variably modified type (C11 6.8.4.2p1 and 6.8.6.1p1). – Ian Abbott Sep 19 '19 at 13:32