views:

6542

answers:

18

I've always wondered this - why can't you declare variables after a case label in a switch statement? In C++ you can declare variables pretty much anywhere (and declaring them close to first use is obviously a good thing) but the following still won't work:

switch (val)  
{  
case VAL:  
  // This won't work
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}

The above gives me the following error (MSC):

initialization of 'newVal' is skipped by 'case' label

This seems to be a limitation in other languages too. Why is this such a problem?

A: 

because you can't guarentee that the variable will be declared if the code doesn't execute that portion of the switch statement.

Kevin
Irrelevant. You can put a declaration in an if () clause. Some compilers will warn that you have used a variable out of scope, but that is another issue.
Matthew Schinckel
In a statically typed langauge? Its out of scope. Can you provide and example?
Kevin
Other languages have different behaviours, but in C++ this issue has nothing to do with the declaration.
Richard Corden
+12  A: 

The whole switch statement is in the same scope, to get around it, do this:

switch (val)  
{  
case VAL:  
{
  // This **will** work
  int newVal = 42;  
}
  break;
case ANOTHER_VAL:  
  ...
  break;
}

Note the brackets

Mark Ingram
Change the "This wont work" comment. Phew SO is really fast.
Vulcan Eager
+7  A: 

Try this:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
Dan Shield
A: 

I believe the issue at hand is that is the statement was skipped, and you tried to use the var elsewhere, it wouldn't be declared.

William Keller
+4  A: 

You can declare variables within a switch statement if you start a new block:

switch (thing)
{ 
  case A:
  {
    int i = 0;  // Completely legal
  }
  break;
}

The reason is to do with allocating (and reclaiming) space on the stack for storage of the local variable(s).

Seb Rose
The variable can be declared, but it cannot be initialized. Also, I'm pretty sure that the issue does not relate in anyway to the stack and local variables.
Richard Corden
+7  A: 

You can't do this, because case labels are actually just entry points into the containing block.

This is most clearly illustrated by Duff's device. Here's some code from Wikipedia:

strcpy(char *to, char *from, size_t count) {
    int 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);
    }
}

Notice how the case labels totally ignore the block boundaries. Yes, this is evil. But this is why your code example doesn't work. Jumping to a case label is the same as using goto, so you aren't allowed to jump over a local variable with a constructor.

As several other posters have indicated, you need to put in a block of your own:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }
emk
Man, thanks for this example! (Duff's device)I never realized how far one can go with C.
Georgi Kirilov
This Duff's device implementation has a bug that makes it extremely slow: count is type int so the % must perform a real division/modulo operation. Make count unsigned (or better yet, always use size_t for counts/indices) and the problem goes away.
R..
@R..: What?! In a two's complement system, signedness doesn't affect modulos by powers of 2 (it's just an AND on the bottom bits), and doesn't affect divisions by powers of 2 as long as your processor architecture has an arithmetic right-shift operation (`SAR` in x86, versus `SHR` which is for unsigned shifts).
Chris Jester-Young
@Chris: I believe he means when the compiler must allow for negative values where "just an AND on the bottom bits" doesn't hold; for example, -1 % 8 gives -1 on this two's complement system using g++ (the sign in this case is implementation defined per 5.6/4).
Roger Pate
Chris Jester-Young
Well it's still considerably slower than just a single bitwise and. On modern cpus, I wouldn't be surprised if that code were almost as slow as the division. My point stands: you should use unsigned types or write the bitwise and yourself.
R..
@Roger: the sign is no longer implementation-defined. C99 defines division with negative numbers, and defines it the wrong way, so that this nasty behavior is now required by the standard. :-(
R..
@Chris: I agree with you that R is exaggerating the impact; I only saw your comment and knew a simple AND didn't suffice.
Roger Pate
@R..: The original code in this comment was from Wikipedia, as noted above. I've just fixed it to use size_t. Thank you for pointing out the problems with C99's % operator; that's one worth remembering.
emk
A: 

newVal exists in the entire scope of the switch but is only initialised if the VAL limb is hit. If you create a block around the code in VAL it should be OK.

marijne
+3  A: 

The entire section of the switch is a single declaration context. You can't declare a variable in a case statement like that. Try this instead:

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}
The variable can be declared, but it cannot be initialized.
Richard Corden
+110  A: 

Case statements are only 'labels'. This means the compiler will interpret this as a jump directly to the label.The problem here is one of scope. Your curly brackets define the scope as everything inside the 'switch' statement. This means that you are left with a scope where a jump will be performed further into the code skipping the initialization. The correct way to handle this is to define a scope specific to that case statement and define your variable within it.

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
Thomas
Probably worth noting that if you don't need the extra scope then don't declare it though :) Opening a new scope isn't without a cost as it will frequently entail a new stack frame.
workmad3
Relative to opening a new scope - favor readability and consistency in the code. In the old days, you might have automatically got an "extra" stack frame, but now that should not be the case for any decent optimizing compiler.
Tall Jeff
I agree with Jeff - it is all too easy to "assume" scope when reading a switch statement because of the indenting style that most people use. My own style is to always open a new scope for each case/default if it is more than one line long.
Bids
workmad3 - Can you find me any C++ compiler at all that will generate a new stack frame if you don't declare any new variables?You worried me briefly, but none of G++ 3.1, Visual C++ 7 or Intel C++ 8 will generate any code for new scopes where you don't declare any variables.
Chris Jefferson
You sir, just made my day much easier!
kigurai
+2  A: 

If your code says "int newVal=42" then you would reasonably expect that newVal is never uninitialised. But if you goto over this statement (which is what you're doing) then that's exactly what happens - newVal is in-scope but has not been assigned.

If that is what you really meant to happen then the language requires to make it explicit by saying "int newVal; newVal = 42;". Otherwise you can limit the scope of newVal to the single case, which is more likely what you wanted.

It may clarify things if you consider the same example but with "const int newVal = 42;"

+2  A: 

Consider:

switch(val)
{
case VAL:
   int newVal = 42;
default:
   int newVal = 23;
}

In the absence of break statements, sometimes newVal gets declared twice, and you don't know whether it does until runtime. My guess is that the limitation is because of this kind of confusion. What would the scope of newVal be? Convention would dictate that it would be the whole of the switch block (between the braces).

I'm no C++ programmer, but in C:

switch(val) {
    int x;
    case VAL:
        x=1;
}

Works fine. Declaring a variable inside a switch block is fine. Declaring after a case guard is not.

slim
A: 

New variables can be decalared only at block scope. You need to write something like this:

case VAL:  
  // This will work
  {
  int newVal = 42;  
  }
  break;

Of course, newVal only has scope within the braces...

Cheers, Ralph

+27  A: 

Ok. Just to clarify this strictly has nothing to do with the declaration. It relates only to "jumping over the initialization" (ISO C++ '03 6.7/3)

A lot of the posts here have mentioned that jumping over the declaration may result in the variable "not being declared". This is not true. An POD object can be declared without an initializer but it will have an indeterminate value. For example:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' initialized to 0, but this statement
            // is jumped when 'i == 1'
     break;
   case 1:
     ++j;   // 'j' is in scope here - but it has an indeterminate value
     break;
}

Where the object is a non-POD or aggregate the compiler implicitly adds an initializer, and so it is not possible to jump over such a declaration:

class A {
public:
  A ();
};

switch (i)  // Error - jumping over initialization of 'A'
{
   case 0:
     A j;   // Compiler implicitly calls default constructor
     break;
   case 1:
     break;
}

This limitation is not limited to the switch statement. It is also an error to use 'goto' to jump over an initialization:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

A bit of trivia is that this is a difference between C++ and C. In C, it is not an error to jump over the initialization.

As others have mentioned, the solution is to add a nested block so that the lifetime of the variable is limited to the individual case label.

Richard Corden
"Error jumping over initialization"??? Not with my GCC. It may give a "j may be used unitialized" warning when using j below label, but there is no error. However, in case of switch, there is an error (a hard error, not a weak warning).
Mecki
@Mecki: It is illegal in C++. ISO C++ '03 - 6.7/3: "...A program that jumps from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer (8.5)."
Richard Corden
Yes, but it is not illegal in C (at least gcc says it's not). j will be uninitialized (have some random number), but the compiler compiles it. However, in case of the switch statement, the compiler won't even compile it and I fail to see the difference between a goto/label case and a switch case.
Mecki
@Mecki: In general a single compiler behaviour does not necessarily reflect whtat is actually allowed by the language. I've checked both C'90 and C'99 and both standards include an example with a jump over initialization in a switch statement.
Richard Corden
+9  A: 

Most of the replies so far are wrong in one respect: you can declare variables after the case statement, but you can't initialize them:

case 1:
    int x; // Works
    int y = 0; // Error, initialization is skipped by case
    break;
case 2:
    ...

As previously mentioned, a nice way around this is to use braces to create a scope for your case.

MrZebra
+3  A: 

My favorite evil switch trick is to use an if(0) to skip over an unwanted case label.

switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}

But very evil.

Jeremy
Why would you do this?
Landon
Very nice. Example of why: case 0 and case 1 might for instance initializing a variable differently that is then used in case 2.
hlovdal
+1  A: 

I just wanted to emphasize slim's point. A switch construct creates a whole, first-class-citizen scope. So it is posible to declare (and initialize) a variable in a switch statement before the first case label, without an additional bracket pair:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}
+1  A: 

So far the answers have been for C++.

For C++, you can't jump over an initialization. You can in C. However, in C, a declaration is not a statement, and case labels have to be followed by statements.

So, valid (but ugly) C, invalid C++

switch (something)
{
  case 1:; // Ugly hack empty statement
    int i = 6;
    do_stuff_with_i(i);
    break;
  case 2:
    do_something();
    break;
  default:
    get_a_life();
}

Conversly, in C++, a declaration is a statement, so the following is valid C++, invalid C

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}
Peter
A: 

Interesting that this is fine:

switch (i)
{
case 0:
int j;
j = 7;
break;

case 1:
break; }

... but this isn't:

switch (i)
{
case 0:
int j = 7;
break;

case 1:
break; }

I get that a fix is simple enough, but I'm not understanding yet why the first example doesn't bother the compiler. As was mentioned earlier (2 years earlier hehe), declaration is not what causes the error, even despite the logic. Initialisation is the problem. If the variable is initialised and declared on the different lines, it compiles.

Dan