C Language switch () Statements


Example

switch statements are useful when you want to have your program do many different things according to the value of a particular test variable.

An example usage of switch statement is like this:

int a = 1;

switch (a) {
case 1:
    puts("a is 1");
    break;
case 2:
    puts("a is 2");
    break;
default:
    puts("a is neither 1 nor 2");
    break;
}

This example is equivalent to

int a = 1;

if (a == 1) {
    puts("a is 1");
} else if (a == 2) {
    puts("a is 2");
} else {
    puts("a is neither 1 nor 2");
}

If the value of a is 1 when the switch statement is used, a is 1 will be printed. If the value of a is 2 then, a is 2 will be printed. Otherwise, a is neither 1 nor 2 will be printed.

case n: is used to describe where the execution flow will jump in when the value passed to switch statement is n. n must be compile-time constant and the same n can exist at most once in one switch statement.

default: is used to describe that when the value didn't match any of the choices for case n:. It is a good practice to include a default case in every switch statement to catch unexpected behavior.

A break; statement is required to jump out of the switch block.

Note: If you accidentally forget to add a break after the end of a case, the compiler will assume that you intend to "fall through" and all the subsequent case statements, if any, will be executed (unless a break statement is found in any of the subsequent cases), regardless of whether the subsequent case statement(s) match or not. This particular property is used to implement Duff's Device. This behavior is often considered a flaw in the C language specification.

Below is an example that shows effects of the absence of break;:

int a = 1;

switch (a) {
case 1:
case 2:
    puts("a is 1 or 2");
case 3:
    puts("a is 1, 2 or 3");
    break;
default:
    puts("a is neither 1, 2 nor 3");
    break;
}

When the value of a is 1 or 2, a is 1 or 2 and a is 1, 2 or 3 will both be printed. When a is 3, only a is 1, 2 or 3 will be printed. Otherwise, a is neither 1, 2 nor 3 will be printed.

Note that the default case is not necessary, especially when the set of values you get in the switch is finished and known at compile time.

The best example is using a switch on an enum.

enum msg_type { ACK, PING, ERROR };
void f(enum msg_type t)
{
  switch (t) {
  case ACK:
    // do nothing
    break;
  case PING:
    // do something
    break;
  case ERROR:
    // do something else
    break;
  }
}

There are multiple advantages of doing this:

  • most compilers will report a warning if you don't handle a value (this would not be reported if a default case were present)
  • for the same reason, if you add a new value to the enum, you will be notified of all the places where you forgot to handle the new value (with a default case, you would need to manually explore your code searching for such cases)
  • The reader does not need to figure out "what is hidden by the default:", whether there other enum values or whether it is a protection for "just in case". And if there are other enum values, did the coder intentionally use the default case for them or is there a bug that was introduced when he added the value?
  • handling each enum value makes the code self explanatory as you can't hide behind a wild card, you must explicitly handle each of them.

Nevertheless, you can't prevent someone to write evil code like:

enum msg_type t = (enum msg_type)666; // I'm evil

Thus you may add an extra check before your switch to detect it, if you really need it.

void f(enum msg_type t)
{
   if (!is_msg_type_valid(t)) {
      // Handle this unlikely error
   }

   switch(t) { 
    // Same code than before
   }
}