Home > Net >  Why C allows defining objects under case labels even though those cases don't match the check
Why C allows defining objects under case labels even though those cases don't match the check

Time:10-04

If I declare/define an object in a if single statement then that object is visible only within the if statement and it is defined and allocated only if the if condition succeeds.

if(true)
   int x = 10;
std::cout << x << '\n'; // error: x is not in scope

This is really so logical but why this doesn't apply to case statement too?

char c = 'b';

switch(c){
   case 'a':
       int value; // ok. not-initialized
   break;
   case 'b':
       value = 100; // why still visible and when initialized? (this is an assignment)
       std::cout << value << '\n'; // 100
   break;
}
  • As you can see c has the value b so the first case label case 'a' is not executed so when value is defined as long as an object is defined when control passes through its definition?

  • Why value is visible under case label case 'b' as long as it is defined under a case label that is not executed?

  • What is the point in allowing that? (defining objects even the case label under which they are defined doesn't match)?

CodePudding user response:

The block of the switch statement starts at opening curly brace and closes at closing one. The break instructions do not mark any end of block.

So both cases belongs to the same instruction block and share the same local variables.

To limit the variable visibility to a block you need to add more curly braces:

switch(c){
   case 'a':{
       int value; // ok. not-initialized
   }
   break;
   case 'b':{
       value = 100; // You should get an error now
       std::cout << value << '\n'; // 100
   }
   break;
}

So the remaining question is why break is not an end of block? I would say that because break may be conditional.

switch(c){
   case 'a':
       int value; // ok. not-initialized
       if (condition)
           break;
   case 'b':
       value = 100; // It is normal in this case to be able to refer to previously defined variable 
       std::cout << value << '\n'; // 100
   break;
}

Though I don't see a real use case for such code!

CodePudding user response:

A switch statement and an if statement are relatively similar on how they are compiled, that is they both translate into a conditional jump in assembly. The main difference is that if branches are always mutually exclusive - if you enter the if, you are never entering the else - but switch branches are not. So it makes sense for the compiler to behave accordingly.

We usually insert a break at the end of each case because otherwise the instructions from the following cases would be executed too. For example:

int x = 1;
switch(x) {
    case 1:
        std::cout << "x was 1";
        // No break!
    case 2:
        std::cout << "... But was it also 2?" << std::endl;
        break;
    default:
        break;
}

Spookily outputs:

x was 1... But was it also 2?

Basically switch is a jump instruction, case specifies where to land but says nothing about jumping out again. That job is done by break.

Using the dreaded goto statements and labels, a very similar code would be this:

int x = 1;
if (x == 1)
    goto x_was_1;
else if (x == 2)
    goto x_was_2;
else
    goto out;
x_was_1:
std::cout << "x was 1";
x_was_2:
std::cout << "... But was it also 2?" << std::endl;
goto out;
out:
// Rest of the code...

Which outputs the same thing. You can also see how adding a break is equivalent to adding goto out in this case.

In conclusion, the content of a switch statement is a single code block and all the variables declared inside it are visible throughout because you may traverse the whole block. Just like you expect variables to be seen below a break in a while loop, you can expect them to be visible in this case too. Though I believe using this is extremely bad practice, very unreadable and likely useless.

  • Related