Switch statement: must default be the last case?
Asked Answered
C

12

216

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 behavior) 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.

Charleencharlemagne answered 24/6, 2010 at 12:57 Comment(10)
+1 Never even considered that behaviourCornall
@Péter Török: you mean if value == 2 it will return 6 ?Flareup
There's something very goto (and therefore evil) about using this behaviour - I'd suggest avoiding it.Ledbetter
@Péter Török no, the order does not matter - if value matches the constant in any case label, then control will jump to that statement following the label, otherwise control will jump to the statement following the default label if present.Joviality
I have to agree with Jon Cage, it's not really readable code. Imho, fall-throughs are always confusing, this one makes it even worse. Feels to me the saying "Programs should be written for people to read, and only incidentally for machines to execute." fits here perfectly.Decor
@Jon Cage goto isn't evil. Cargo cult followers are! You could not imagine to what extremes people can go to avoid goto because it is alledgedly so evil, making a real unreadable mess of their code.Readily
Perhaps evil was a bad choice of words, maybe easily abused or easily misunderstood. There's no reason why avoiding goto's needs to lead to less readable code and generally, if you need goto's I'd suggest it's a sign that the code code be better structured in the first place. If you've got any concrete examples which disprove my point I'd be really interested to see them. I'm not bashing here, I've just had to fix some nasty bugs caused by goto's and similar language constructs in the past :-)Ledbetter
I use goto mainly to simulate something like a finally clause in functions, where ressources (files, memory) have to be released when stopping, and repeating for every error case a list of free and close doesn't help for readability. There's though one use of goto that I'd like to avoid but can't, is when I want to break out of a loop and I'm within a switch in that loop.Readily
Best question ever! Your direct question is answered by everyone here. Your indirect question (should I?) is answered indirectly by the count of people who came here and argued. Now, if you do this in code, you are doing it despite being asked not to by many devs who have to read code. Its terrible practice because many people will be confused enough to end up at stackoverflow when they see your code. Any benefit is minor at best and probably dubious. Cost-benefit is super low. Also, dont do it because default is an exception to the rule. default is weirdly different than case.Mercer
@Dave: yes it is very interesting how many people are aguing about it or are not sure about what is happening here. That alone is a strong hint not to use this practice. Obviously a lot of people can't read or understand this code (including me before I asked this question).Charleencharlemagne
K
94

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.

Kiblah answered 24/6, 2010 at 13:43 Comment(6)
@HeathHunnicutt You clearly didn't understand the purpose of the example. The code isn't made up by this poster, but taken straight from the C standard, as an illustration of how weird switch statements are, and how bad practice will lead to bugs. If you had bothered to read the text below the code, you'd realize as much.Caster
+1 to compensate for the downvote. Downvoting someone from citing the C standard seems quite harsh.Caster
@Caster I'm not down-voting the C standard, and I didn't overlook anything as you suggest. I down-voted the bad pedagogy of using a bad, and unneeded, example. In particular, that example relates to a different situation entirely than was asked about. I could go on, but "thanks for your feedback."Augustineaugustinian
Intel tells you to place the most frequent code first in a switch statement at Branch and Loop Reorganization to Prevent Mispredicts. I'm here because I have a default case dominating other cases by about 100:1, and I don't know if its valid or undefined to make default the first case.Filibertofilibuster
@Filibertofilibuster I am not sure what you mean by Intel. If you mean intelligence I will call it hypothesis. I had the same thinking, but later reading states that unlike if statements, switch statements are random access. So the last case is not slower to reach than the first. This is accomplished by hashing the constant case values. That is why switch statements are faster than if statements when the branches are alot.Riella
@JaveneCPPMcGowan I'm sure by Intel jww mean the firm who produces computer chips, not the common word intelligence. And your thinking is not entirely right: compiler takes the liberty to turn some switch cases into parametrized (register indirect) unconditional jmp, and to turn other switch cases into conditional branches.Cleasta
M
111

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.

Metrical answered 24/6, 2010 at 13:5 Comment(1)
This is exactly the scenario where I usually place default somewhere other than the end... there's a logical order to the explicit cases (1, 2, 3) and I want the default to behave exactly the same as one of the explicit cases that isn't the last one.Fishnet
K
94

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.

Kiblah answered 24/6, 2010 at 13:43 Comment(6)
@HeathHunnicutt You clearly didn't understand the purpose of the example. The code isn't made up by this poster, but taken straight from the C standard, as an illustration of how weird switch statements are, and how bad practice will lead to bugs. If you had bothered to read the text below the code, you'd realize as much.Caster
+1 to compensate for the downvote. Downvoting someone from citing the C standard seems quite harsh.Caster
@Caster I'm not down-voting the C standard, and I didn't overlook anything as you suggest. I down-voted the bad pedagogy of using a bad, and unneeded, example. In particular, that example relates to a different situation entirely than was asked about. I could go on, but "thanks for your feedback."Augustineaugustinian
Intel tells you to place the most frequent code first in a switch statement at Branch and Loop Reorganization to Prevent Mispredicts. I'm here because I have a default case dominating other cases by about 100:1, and I don't know if its valid or undefined to make default the first case.Filibertofilibuster
@Filibertofilibuster I am not sure what you mean by Intel. If you mean intelligence I will call it hypothesis. I had the same thinking, but later reading states that unlike if statements, switch statements are random access. So the last case is not slower to reach than the first. This is accomplished by hashing the constant case values. That is why switch statements are faster than if statements when the branches are alot.Riella
@JaveneCPPMcGowan I'm sure by Intel jww mean the firm who produces computer chips, not the common word intelligence. And your thinking is not entirely right: compiler takes the liberty to turn some switch cases into parametrized (register indirect) unconditional jmp, and to turn other switch cases into conditional branches.Cleasta
C
58

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 are cases with a defined set of input values where there are 'exceptions' and normal cases. 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.

Another reason is that the order of the cases may change the compiled code behavior, and that matters for performances. Most compilers will generate compiled assembly code in the same order as the code appears in the switch. That makes the first case very different from the others: all cases except the first one will involve a jump and that will empty processor pipelines. You may understand it like branch predictor defaulting to running the first appearing case in the switch. If a case if much more common that the others then you have very good reasons to put it as the first case.

Reading comments it's the specific reason why the original poster asked that question after reading Intel compiler Branch Loop reorganisation about code optimisation.

Then it will become some arbitration between code readability and code performance. Probably better to put a comment to explain to future reader why a case appears first.

Cassius answered 24/6, 2010 at 13:26 Comment(11)
+1 for giving a (good) example without the fallthrough behaviour.Decor
...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.Ledbetter
@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).Cassius
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.Cassius
gcc has it as an extension. You can write case 3 .. 5Readily
@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.Cassius
@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?Ilona
@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.Cassius
I'm not a C programmer, but should default have two spaces before it?Stacy
@Andrew Grimm: C is mostly space insensitive (the mostly is here because in some case you need at least 1 space to separate two words like in int v;). Actually, for my indentation to be consistent, default should be aligned with case. I will edit my answer, thanks for your comment.Cassius
@kriss: I was half tempted to say "I'm not a python programmer either!" :)Stacy
B
20

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

Beria answered 24/6, 2010 at 13:1 Comment(6)
-1: This smells of evil to me. It would be better to split the code into a pair of switch statements.Ledbetter
@John Cage: putting me a -1 here is nasty. It is not my fault that this is valid code.Beria
just curious, I would like to know under which circumstances it is useful?Metrical
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.Ledbetter
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.Beria
@Metrical LLVM uses default first extensively as a way to avoid branch mispredicts. Unfortunately, LLVM's is implemented on C++, wish may vary from the C standard. You can check it on the following link anyways reviews.llvm.org/rL315525Denverdeny
P
11

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);
  }
}
Persephone answered 24/6, 2010 at 13:18 Comment(2)
And for anyone who's not familiar with Duff's device this code is completely unreadable...Decor
Duff's Device is a perfect example of "Just because you can, doesn't mean you should"Colbert
E
9

One scenario where I would consider it appropriate to have the default case located somewhere else than at the end of a switch statement is in a state machine where an invalid state should reset the machine and proceed as though it was the initial state. For example:

switch(widget_state)
{
  default:  /* Fell off the rails--reset and continue */
    widget_state = WIDGET_START;
    /* Fall through */
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
}

An alternative arrangement, if an invalid state should not reset the machine but should be readily identifiable as an invalid state:

switch(widget_state)
{
  case WIDGET_IDLE:
    widget_ready = 0;
    widget_hardware_off();
    break;
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
  default:
    widget_state = WIDGET_INVALID_STATE;
    /* Fall through */
  case WIDGET_INVALID_STATE:
    widget_ready = 0;
    widget_hardware_off();
    ... do whatever else is necessary to establish a "safe" condition
}

Code elsewhere may then check for widget_state == WIDGET_INVALID_STATE and provide whatever error-reporting or state-reset behavior seems appropriate. For example, the status-bar code could show an error icon, and the "start widget" menu option which is disabled in most non-idle states could be enabled for WIDGET_INVALID_STATE as well as WIDGET_IDLE.

Ealdorman answered 24/6, 2010 at 16:1 Comment(0)
P
6

Chiming in with another example: This can be useful if "default" is an unexpected case, and you want to log the error but also do something sensible. Example from some of my own code:

  switch (style)
  {
  default:
    MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
  case SOLID:
    return Dash(0, RECT_DOT);
  case DASH_SYS:
  {
    Dash ret(shapeLineWidth, dotStyle);
    ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
    return ret;
  }
  // more cases follow
  }
Pursuer answered 9/8, 2012 at 17:54 Comment(0)
C
5

There are cases when you are converting ENUM to a string or converting string to enum in case where you are writing/reading to/from a file.

You sometimes need to make one of the values default to cover errors made by manually editing files.

switch(textureMode)
{
case ModeTiled:
default:
    // write to a file "tiled"
    break;

case ModeStretched:
    // write to a file "stretched"
    break;
}
Correction answered 22/1, 2014 at 21:45 Comment(0)
S
3

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
Siouxie answered 24/6, 2010 at 13:14 Comment(0)
L
1

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]; 
}
Ledbetter answered 24/6, 2010 at 13:6 Comment(8)
There are cases where fall through is really, really a good idea.Readily
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 |= 0xc0; case 1: r[0] = wc; }Readily
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]; } }Readily
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.Readily
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..Ledbetter
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.Readily
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.Readily
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); ;-)Readily
S
0

I had an interesting case where putting the default at the top saved programme space. It was for an Arduino Nano, and saved 8 bytes of flash (RAM was same). FYI the two sets of code are

#if 1 // toggle this 0 or 1
// 3138/265 bytes
uint8_t g_BuiltinLedGlowState = 0; // dropping '= 0' saves nothing
void AdvanceBuiltinLedGlow_3Ph(){
  switch(++g_BuiltinLedGlowState){
  default: 
    g_BuiltinLedGlowState = 0;
    // drop through // break;
  case 0: // bright
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);
    break;
  case 1: // dim
    pinMode(LED_BUILTIN, INPUT_PULLUP);
    break;
  case 2: // off
    pinMode(LED_BUILTIN, INPUT);
    break;
  }
}
#elif 1
// 3146/265 bytes
uint8_t g_BuiltinLedGlowState = 0; // dropping '= 0' saves nothing
void AdvanceBuiltinLedGlow_3Ph(){
  switch(++g_BuiltinLedGlowState){
  case 1: // bright
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);
    break;
  case 2: // dim
    pinMode(LED_BUILTIN, INPUT_PULLUP);
    break;
  case 3: // off
    pinMode(LED_BUILTIN, INPUT);
    // drop through // break;
  default: 
    g_BuiltinLedGlowState = 0;
    break;
  }
}
#endif

// the loop function runs over and over again forever
void loop() {
  Serial.println(g_BuiltinLedGlowState, DEC);
  AdvanceBuiltinLedGlow_3Ph();
  delay(1000);
}
Savdeep answered 15/1, 2022 at 4:5 Comment(0)
C
0

I don't know if you can call it "Ugly" , certainly it is very useful in those cases when you have to write:

    switch (cond) {
       case default: Instruction1;
               ...
               InstructionN;
       case 1: FinalInstruction;
    }

you avoid writing code twice

C answered 19/10, 2022 at 8:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.