A Better Explanation of Switch Case

Discussion in 'Programmer's Corner' started by ke5nnt, Apr 4, 2013.

  1. ke5nnt

    Thread Starter Active Member

    Mar 1, 2009
    384
    15
    Let me start this thread by thanking those of you who have helped me tremendously over the last week by providing in-depth feedback to my other questions. The time you take on your replies shows a genuine desire on your part to help someone who's learning, and it most certainly is appreciated, so thank you.

    Switch Case:

    The books for beginners I have, as well as internet searches reveal little information on switch case statements. I understand the usage, some number or variable is tested, and depending on the result, one of a number of instructions are executed. These pieces of literature don't really go into any depths on exactly how they operate though, which is where my questions come from.

    For instance, I have only recently figured out through testing that an if statement is not a continuous loop like a while statement. The condition is tested and if true, the if instructions are executed once, and not continuously until the program comes back around to the if statement again (assuming it's still true). I have to assume from this, that the switch case is the same way?

    I have seen the switch case examples only in the form of:
    Code ( (Unknown Language)):
    1.  
    2. switch(x)
    3. {
    4. case 1:
    5.     instruction;
    6.     break;
    7. case 2:
    8.     instruction;
    9.     break;
    10. etc...
    11. }
    Can each case contain multiple instructions using braces "{}" or is a case limited to 1 instruction and the break statement.

    Finally, for now... what exactly does the "break" statement do?

    Thanks
     
  2. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    All instruction formats can be considered as a "simple" statement or a "compound" statement.

    For example, this is a simple statement:
    Code ( (Unknown Language)):
    1.  
    2.  x = 123;
    3.  
    A compound statement is a group of statements enclosed by the braces {}, for example:
    Code ( (Unknown Language)):
    1.  
    2. {
    3.    x = 123;
    4.    y = x/2;
    5. }
    6.  
    Anytime you see a simple statement being used, it can be replaced with a compound statement. So in your example of the switch/case structure

    instruction;

    can be replaced with
    Code ( (Unknown Language)):
    1.  
    2. {
    3.   instruction1;
    4.   instruction2;
    5.   instruction3;
    6. }
    7.  
    In a switch/case structure the break; statement is important. This ends the instructions to be executed for that case.
    Without the break; the instructions that follow will be executed.

    The switch/case structure is essentially a sequence of

    Code ( (Unknown Language)):
    1.  
    2. if ( x == case1 )
    3. {
    4. }
    5. else if ( x == case2 )
    6. {
    7. }
    8. else if ( x == case3 )
    9. {
    10. }
    11.  
     
  3. ke5nnt

    Thread Starter Active Member

    Mar 1, 2009
    384
    15
    So what you're saying is that without the break statement, if the "case" is case 1, after case 1 is executed, the program would just execute case 2, then case 3, etc because "break" isn't there?

    If that is true, where does program execution break to? The first instruction after the switch function is closed (i.e. the instruction that follows the switch's right brace)?
     
  4. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    With the break; properly in place, after the case is executed, the switch statement ends.

    Note that there is also a default option when none of the cases match.
     
    ke5nnt likes this.
  5. ke5nnt

    Thread Starter Active Member

    Mar 1, 2009
    384
    15
    Thank you for clearing that up for me.
     
  6. spinnaker

    AAC Fanatic!

    Oct 29, 2009
    4,887
    1,016
    Watch that use of "properly" in place. There times when you may not want a break.

    Example
    Code ( (Unknown Language)):
    1.  
    2. switch(code)
    3. {
    4.       case 0:
    5.          DoSomething0();
    6.          break
    7.  
    8.      case 1:
    9.         DoSomething1();
    10.  
    11.      case 2:
    12.         DoSomething2();
    13.         break;
    14.  
    15.       default:
    16.          DoSomethingElse();
    17.  
    18. }
    19.  
    In this switch statement, if code is 0 then DoSomething0 is executed, if it is 2 then DoSomething2 is executed. But if it is 1 both DoSomething1 and DoSomething 2 are executed. There is nothing wrong with the switch statement if that is your intent.

    Also notice the default keyword. DoSomethingElse gets executed if code is not 0,1,2.
     
    ke5nnt likes this.
  7. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,395
    1,607
    Another thing the switch/case statements can do for you is to use several cases to do the same task. To borrow spin's example:

    Code ( (Unknown Language)):
    1. switch(code)
    2. {
    3.       case 0:
    4.          DoSomethingZer0();
    5.          break;
    6.  
    7.       case 1:
    8.       case 3:
    9.       case 5:
    10.       case 7:
    11.         DoSomethingOdd();
    12.          break;
    13.  
    14.       case 2:
    15.       case 4:
    16.       case 6:
    17.       case 8:
    18.         DoSomethingEven();
    19.         break;
    20.  
    21.       default:
    22.         DoSomethingElse();
    23.         break;
    24. }
    Note even and odd numbers do different tasks, as does zero.

    Also note the default case has a break too. Why? Good practice and defensive programming when comes the day you add some functions and make a new default with a cut n paste and forget the new break needed.
     
  8. THE_RB

    AAC Fanatic!

    Feb 11, 2008
    5,435
    1,305
    Nice example ErnieM, thank you I have never seen that. :)

    I wonder how it compiles? The compiler probably inserts a
    if x==foo goto blah
    for every case instruction?

    I've always consider switch/case as a hastily added C kludge by K&R, and a poor way to program. Your example shows an application where for once it might actually be worth using.
     
  9. Tesla23

    Active Member

    May 10, 2009
    318
    67
    With a good compiler the case statement can be highly efficient, almost always much better that comparisons. I would expect even a modestly reasonable compiler to choose one of N case statements using a binary search with O(log2(N)) comparisons, but a good compiler can do even better in some cases. As this article shows
    http://www.codeproject.com/Articles/100473/Something-You-May-Not-Know-About-the-Switch-Statem
    depending on the label values, the compiler can do much better using jump tables. I know that this article refers to Visual Studio - not an embedded compiler, but there is no reason that embedded compilers couldn't do similar things. I don't have any data on that though. Given that for contiguous case values jump tables are probably more compact than repeated comparisons, they may.
     
  10. WBahn

    Moderator

    Mar 31, 2012
    17,777
    4,805
    A way that may help you visualize how a switch() statement works is the following.

    Let's using the following code snippet:

    Code ( (Unknown Language)):
    1.  
    2. switch(Fred)
    3. {
    4.    case 0:  statement0;
    5.             statement1;
    6.             break;
    7.    case 2:
    8.    case 4:  statement2;
    9.             statement3;
    10.             break;
    11.    case 1:
    12.    case 3:  statement4;
    13.             statement5;
    14.             break;
    15.    default: statement6;
    16.             statement7;
    17.             break;
    18. }
    19.  
    The body of the switch() statement is copied pretty much as is with each case condition being converted to a label and the break statements being converted to GOTOs to the statement just after the body. The the guts become something like:

    Code ( (Unknown Language)):
    1.  
    2.    // BODY of switch() statement
    3.  
    4.    label0:  statement0;
    5.             statement1;
    6.             GOTO endsw;
    7.    label2:
    8.    label4:  statement2;
    9.             statement3;
    10.             GOTO endsw;
    11.    label1:
    12.    label3:  statement4;
    13.             statement5;
    14.             GOTO endsw;
    15.    default: statement6;
    16.             statement7;
    17.             GOTO endsw;
    18.    endsw:
    19.  
    20.    // CODE following switch()
    21.  
    22.  
    All that is left is to deal with the switch parameter, Fred. This can be thought of as just a list of IF..GOTO statements that test if Fred is equal to the integer constant associated with each label followed by a GOTO to that label.

    Code ( (Unknown Language)):
    1.  
    2.    // CONTROL of switch() statement
    3.  
    4.             IF (Fred == 0) GOTO label0;
    5.             IF (Fred == 2) GOTO label2;
    6.             IF (Fred == 4) GOTO label4;
    7.             IF (Fred == 1) GOTO label1;
    8.             IF (Fred == 3) GOTO label3;
    9.             GOTO default;
    10.    
    11.    // BODY of switch() statement
    12.  
    13.    label0:  statement0;
    14.             statement1;
    15.             GOTO endsw;
    16.    label2:
    17.    label4:  statement2;
    18.             statement3;
    19.             GOTO endsw;
    20.    label1:
    21.    label3:  statement4;
    22.             statement5;
    23.             GOTO endsw;
    24.    default: statement6;
    25.             statement7;
    26.             GOTO endsw;
    27.    endsw:
    28.  
    29.    // CODE following switch()
    30.  
    31.  
    Notice that if none of the conditions are satisfied that there is an unconditional jump to the default section. If there isn't a default section, then you can think of it two ways, either that there is an implied default section that happens to be empty, in which case the goto is effectively a jump to the "endsw" label, or that this goto is changed explicitly to an unconditional jump to "endsw". Both are equivalent.

    Compilers can play all kinds of games to make this more efficient, but I believe this conceptually captures all of the behavior of a switch() statement.

    Since the case lablels are required to be unique (at least in C), the order of the IF...GOTO statements is irrelevant in the control section, so those can be reordered by the compiler in some manner to permit efficient implementation.

    However, note that it because obvious that the order of the case statements is NOT irrelevant. At the end of the day, all you have is a block of code and your switch() parameter is just determining where you jump into this block and, at that point, you just start executing the code from that point on until you encounter a break statement, in which case you jump to the statement after the switch(), or you just naturally fall out the bottom and continue on to the next statement that way.

    So consider the following snippet.

    Code ( (Unknown Language)):
    1.  
    2. switch(Fred)
    3. {
    4.    case 5:  Fred << 1;
    5.    case 4:  Fred << 1;
    6.    case 3:  Fred << 1;
    7.    case 2:  Fred << 1;
    8.    case 1:  Fred << 1;
    9. }
    10.  
    This results in

    Fred = Fred * 2^Fred

    if Fred is between 1 and 5 inclusive and leaves it unchanged otherwise.

    I'm not saying this is useful or even adheres to good style, it's just an example to think of how things can cascade if you don't include break statements. This is useful if you can find a way to leverage it, but more importantly it needs to be kept in mind for when you forget to include a break statement somewhere and are trying to figure out why the code is doing something wonky.

    Your ability to debug largely hinges on your ability to correctly recognize what you are telling the code to do (as separate and distinct from what you want the code to do!) and that ability is limited to your understanding of the fundamental behavior of the various programming structures.
     
    ke5nnt likes this.
  11. WBahn

    Moderator

    Mar 31, 2012
    17,777
    4,805
    Most of the time, but not anytime. If the value of the statement is needed, then it can't be embedded in a compound statement. This is principally the case with control structures. For instance, in

    Code ( (Unknown Language)):
    1.  
    2. while(i = f(++x)) loop_code;
    3.  
    the while() statement needs the value of the statement i=f(x). If you turn this into a compound statement, such as

    Code ( (Unknown Language)):
    1.  
    2. while({++x; i = f(x)}) loop_code;
    3.  
    it won't work.

    But if you turn it into a comma delimited statement containing multiple expressions, it will

    Code ( (Unknown Language)):
    1.  
    2. while(++x, i = f(x)) loop_code;
    3.  
    The comma delimited expression statement is something that most people think they've ever heard of yet most have used it many times. Consider

    Code ( (Unknown Language)):
    1.  
    2. for (i = 0, j = 1; i < 10; i++) loop_code;
    3.  
    The reason that the initialization statements have to be separated by commas is because a compound statement is not allowed here (although I'm not aware of any reason that it couldn't be allowed both for the first and third statements since, to the best of my knowledge, the value that the statement evaluates to is not used. Obviously it can't be allowed for the 2nd statement since that value IS used.


    As long as it is understood that, in this format, some code might need to be replicated in multiple if() bodies.
     
  12. WBahn

    Moderator

    Mar 31, 2012
    17,777
    4,805
    Actually, I think I need to modify/retract some of what I just posted.

    If we distinguish between "expressions", "expression-statements", and "statement", then it is true that anyplace a statement can be used a compound statement can be used. The subtle point comes in because while an expression can always be a statement, it is not always a statement.

    For instance in

    x = i + j;

    The "x = i + j" is an expression-statement, but in

    while (x= i+j) the "x=i+j" is not, it is just an expression.

    We tend to think of an assignment "statement" when it is really just an expression in which the operand has a side-effect. I just fell into that trap even though I know better.

    So my counter examples in the prior post are not correct because, in those places, while I am using a string of text that, in another context, would be a statement, it is not a statement within the control structures and hence doesn't violate the rule.
     
  13. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,395
    1,607
    Interesting, but raises a ton of questions.

    What is the value? Left most, right most,something in the middle?

    What is the order of evaluation? Is x plus-plused before f(x) is evaluated, or the reverse? (Right to left,left to right, or compiler dependent?)

    I finally noticed an example of the for statement (perhaps in K&R?) with multiple assignments, but have not had the need for it.
     
  14. WBahn

    Moderator

    Mar 31, 2012
    17,777
    4,805
    The behavior of compound expressions is well defined by the standard. The expressions are evaluated left to right, all side effects must be resolved before proceeding to the next expression, and the value of the compound expression is equal to the value of the last expression evaluated.
     
  15. ke5nnt

    Thread Starter Active Member

    Mar 1, 2009
    384
    15
    Thank you all for the very definitive explanations for switch/case, very thorough and helpful.
     
Loading...