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 valueb
so the first case labelcase 'a'
is not executed so whenvalue
is defined as long as an object is defined when control passes through its definition?Why
value
is visible under case labelcase '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 case
s 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.