views:

517

answers:

8

Consider the following switch statement:

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}

This code compiles, but is it valid (= defined behaviour) for C90/C99? I have never seen code where the default case is not the last case.

EDIT:
As Jon Cage and KillianDS write: this is really ugly and confusing code and I am well aware of it. I am just interested in the general syntax (is it defined?) and the expected output.
Thanks for your contributions!

+6  A: 

yes, this is valid, and under some circumstances it is even useful. Generally, if you don't need it, don't do it.

Jens Gustedt
-1: This smells of evil to me. It would be better to split the code into a pair of switch statements.
Jon Cage
@John Cage: putting me a -1 here is nasty. It is not my fault that this is valid code.
Jens Gustedt
@jens : just curious, I would like to know under which circumstances it is useful?
Salil
The -1 was aimed at your assertion of it being useful. I'll change it to a +1 if you can provide a valid example to back up your claim.
Jon Cage
Sometimes when switching for an errno that we got in return from some system function. Say we have one case where we know for good that we have to do a clean exit, but this clean exit might require some lines of coding that we don't want to repeat. But suppose also we also have a lot of other exotic error codes that we don't want to handle individually. I would consider just putting a perror in the default case and let it run through to the other case and exit cleanly. I don't say you should do it like that. It is just a matter of taste.
Jens Gustedt
+20  A: 

The case statements and the default statement can occur in any order in the switch statement. The default clause is an optional clause that is matched if none of the constants in the case statements can be matched.

Good Example :-

switch(5) {
  case 1:
    echo "1";
    break;
  case 2:
  default:
    echo "2, default";
    break;
  case 3;
    echo "3";
    break;
}


Outputs '2,default'

very useful if you want your cases to be presented in a logical order in the code (as in, not saying case 1, case 3, case 2/default) and your cases are very long so you do not want to repeat the entire case code at the bottom for the default

Salil
+1  A: 

It's valid, but rather nasty. I would suggest it's generally bad to allow fall-throughs as it can lead to some very messy spaghetti code.

It's almost certainly better to break these cases up into several switch statements or smaller functions.

[edit] @Tristopia: Your example:

Example from UCS-2 to UTF-8 conversion 

r is the destination array, 
wc is the input wchar_t  

switch(utf8_length) 
{ 
    /* Note: code falls through cases! */ 
    case 3: r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
    case 2: r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 
    case 1: r[0] = wc;
}

would be clearer as to it's intention (I think) if it were written like this:

if( utf8_length >= 1 )
{
    r[0] = wc;

    if( utf8_length >= 2 )
    {
        r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 

        if( utf8_length == 3 )
        {
            r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
        }
    }
}   

[edit2] @Tristopia: Your second example is probably the cleanest example of a good use for follow-through:

for(i=0; s[i]; i++)
{
    switch(s[i])
    {
    case '"': 
    case '\'': 
    case '\\': 
        d[dlen++] = '\\'; 
        /* fall through */ 
    default: 
        d[dlen++] = s[i]; 
    } 
}

..but personally I would split the comment recognition into it's own function:

bool isComment(char charInQuestion)
{   
    bool charIsComment = false;
    switch(charInQuestion)
    {
    case '"': 
    case '\'': 
    case '\\': 
        charIsComment = true; 
    default: 
        charIsComment = false; 
    } 
    return charIsComment;
}

for(i=0; s[i]; i++)
{
    if( isComment(s[i]) )
    {
        d[dlen++] = '\\'; 
    }
    d[dlen++] = s[i]; 
}
Jon Cage
There are cases where fall through is really, really a good idea.
tristopia
Show me.. (15 char limit)
Jon Cage
Example from UCS-2 to UTF-8 conversion `r` is the destination array, `wc` is the input `wchar_t` switch(utf8_length) { /* Note: code falls through cases! */ case 3: r[2] = 0x80 | (wc wc >>= 6; wc |= 0x800; case 2: r[1] = 0x80 | (wc wc >>= 6; wc |= 0xc0; case 1: r[0] = wc; }
tristopia
Here another, a string copy routine with character escaping: `for(i=0; s[i]; i++) { switch(s[i]) { case '"': case '\'': case '\\': d[dlen++] = '\\'; /* fall through */ default: d[dlen++] = s[i]; } }`
tristopia
Yes, but this routine is one of our hotspots, this was the fastest, portable (we won't do assembly) way to implement it. It has only 1 test for any UTF length, yours has 2 or even 3.Besides, I didn't come up with it, I took it from BSD.
tristopia
If you really need breakneck speed and can justify it against readability then I'd say you have the right approach. I'd be suprised if there were a benchmarkable difference between the two though..
Jon Cage
Yes there were, especially in conversions in Bulgarian and Greek (on Solaris SPARC) and text with our internal markup (which is 3 byte UTF8). Admitted, in toto it's not much and has become irrelevant since our last hardware update, but at the time it was written it made some difference.
tristopia
The second example though is not good, you put a bug in it. The copy routine doesn't replace the characters ", ' and \ with a \ but inserts a \ before them, that's the reason of the fall through. In your rewrite you should remove the `else`.
tristopia
Ah, I see. I blame lack of comments in the code ;-) Code updated..
Jon Cage
I stripped them before posting because of the comment limits here. This said, this example comes from a part of our project that is not for the faint of heart. There are other things in there that are not quite academic, here for example the statement allocating that `d` buffer: `d = memcpy(calloc(olen*2+10,1), "echo \"", 6);` ;-)
tristopia
+1  A: 

The "default" condition can be anyplace within the switch that a case clause can exist. It is not required to be the last clause. I have seen code that put the default as the first clause. The "case 2:" gets executed normally, even though the default clause is above it.

As a test, I put the sample code in a function, called test(int value){} and ran:

  printf("0=%d\n", test(0));
  printf("1=%d\n", test(1));
  printf("2=%d\n", test(2));
  printf("3=%d\n", test(3));
  printf("4=%d\n", test(4));

The output is:

0=2
1=1
2=4
3=8
4=10
Scott Thomson
+5  A: 

There's no defined order in a switch statement. You may look at the cases as something like a named label, like a goto label. Contrary to what people seem to think here, in the case of value 2 the default label is not jumped to. To illustrate with a classical example, here is Duff's device, which is the poster child of the extremes of switch/case in C.

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);
  }
}
tristopia
And for anyone who's not familiar with Duff's device this code is completely unreadable...
KillianDS
+12  A: 

It's valid and very useful in some cases.

Consider the following code:

switch(poll(fds, 1, 1000000)){
   default:
    // here goes the normal case : some events occured
   break;
   case 0:
    // here goes the timeout case
   break;
   case -1:
     // some error occurred, you have to check errno
}

The point is that the above code is more readable and efficient than cascaded if. You could put default at the end, but it is pointless as it will focus your attention on error cases instead of normal cases (which here is the default case).

Actually, it's not such a good example, in poll you know how many events may occur at most. My real point is that there is cases with a defined set of input values where there is 'exceptions' and normal case. If it's better to put exceptions or normal cases at front is a matter of choice.

In software field I think of another very usual case : recursions with some terminal values. If you can express it using a switch, default will be the usual value that contains recursive call and distinguished elements (individual cases) the terminal values. There is usually no need to focus on terminal values.

kriss
+1 for giving a (good) example without the fallthrough behaviour.
KillianDS
+1: Good example
Jon Cage
...thinking about it though, I'm not convinced having the default at the top is good because very few people would be looking for it there. It might be better to assign the return to a variable and handle success in one side of an if and errors in the other side with a case statement.
Jon Cage
@Jon: just write it. You add syntaxic noise without any readability benefit. And, if default is at top, there is really no need to look at it, it's really obvious (it could be more tricky if you put it in the middle).
kriss
+1 for making it useful.
Tim Post
By the way I do not really like the C switch/case syntax. I would much prefer to be able to put several labels after one case instead of be obliged to put several successive `case`. What is depressing is that it looks just as syntaxic sugar and won't break any existing code if supported.
kriss
gcc has it as an extension. You can write `case 3 .. 5`
tristopia
@tristopia: cool. Thanks to tell. gcc has an history of nice c extensions. I remember a time when gcc supported nested functions in C. Too bad such extensions does not get into standard.
kriss
@kriss: What I find really depressing is the way the C switch statement got copied. Stroustrup had an excuse, since he deliberately trying to maintain as much C compatibility as possible. What excuse do other language designers have?
David Thornley
@David: C switch has it's points, but that's probably a suggest for another question. I do not like languages that replace switch by a construction with non constant labels, then it's no better than C's. On the other hand some kind of constant pattern matching for labels (like in Haskell) would be really nice to have in a C like language and would cover what switch currently does.
kriss
+6  A: 

The C99 standard is not explicit about this, but taking all facts together, it is perfectly valid.

A case and default label are equivalent to a goto label. See 6.8.1 Labeled statements. Especially interesting is 6.8.1.4, which enables the already mentioned Duff's Device:

Any statement may be preceded by a prefix that declares an identifier as a label name. Labels in themselves do not alter the flow of control, which continues unimpeded across them.

Edit: The code within a switch is nothing special; it is a normal block of code as in an if-statement, with additional jump labels. This explains the fall-through behaviour and why break is necessary.

6.8.4.2.7 even gives an example:

switch (expr) 
{ 
    int i = 4; 
    f(i); 
case 0: 
    i=17; 
    /*falls through into default code */ 
default: 
    printf("%d\n", i); 
} 

In the artificial program fragment the object whose identifier is i exists with automatic storage duration (within the block) but is never initialized, and thus if the controlling expression has a nonzero value, the call to the printf function will access an indeterminate value. Similarly, the call to the function f cannot be reached.

The case constants must be unique within a switch statement:

6.8.4.2.3 The expression of each case label shall be an integer constant expression and no two of the case constant expressions in the same switch statement shall have the same value after conversion. There may be at most one default label in a switch statement.

All cases are evaluated, then it jumps to the default label, if given:

6.8.4.2.5 The integer promotions are performed on the controlling expression. The constant expression in each case label is converted to the promoted type of the controlling expression. If a converted value matches that of the promoted controlling expression, control jumps to the statement following the matched case label. Otherwise, if there is a default label, control jumps to the labeled statement. If no converted case constant expression matches and there is no default label, no part of the switch body is executed.

Secure
-1 for gratuitously introducing code before a first case statement, which does not address the OP question but does promote a questionable practice. https://www.securecoding.cert.org/confluence/display/seccode/MSC35-C.+Do+not+include+any+executable+statements+inside+a+switch+statement+before+the+first+case+label
Heath Hunnicutt
+3  A: 
supercat