Friday, March 14, 2014

Weird compiler error: C2360: initialization is skipped by label

I ran into a new error the other day that was non-obvious at first glance.

I had code structured something like (greatly simplified by way of example):
void MyFunction()
{
     int a = GetValueOfA();
     switch(a)
     {
          case 1:
               int b = 2;
               DoSomething(b);
               break;
          case 2:
               int c = 3;
               DoSomethingElse(c);
               break;
     }
}
This gave me an error on the second case "error C2360: initialization of 'b' is skipped by 'case' label." *

What?!? This is one of those cases where the message is technically accurate after you understand what it says but utterly useless to explain what is going on or what the solution is at first glance. Or perhaps I'm slow. I stared at this for a bit before I comprehended what it was trying to tell me.

The root of the issue is that, while the case statements appear to be in their own scope, they aren't. (I'm sure I knew this at some point, but the memory was overridden long ago. Or perhaps I have too many languages floating around in my head.) The scope for variables inside a switch statement is all the cases, not just the current case. Therefore, in the code above, variables b and c are available anywhere inside the switch statement. The error says b is defined, but may not be initialized properly, when a = 2.

All the solutions involve changing the scope of b and c. There are a couple immediately obvious solutions:

1) Put braces around the contents of the case statements to limit their scope.
void MyFunction()
{
     int a = GetValueOfA();
     switch(a)
     {
          case 1:
               {
                    int b = 2;
                    DoSomething(b);
               }
               break;
          case 2:
               {
                    int c = 3;
                    DoSomethingElse(c);
               }
               break;
     }
}
2) Put the contents of the case statements in their own functions, another way of limiting their scope.
void PerformCase1()
{
     int b = 2;
     DoSomething(b);
}

void PerformCase2()
{
     int c = 3;
     DoSomethingElse(c);
}

void MyFunction()
{
     int a = GetValueOfA();
     switch(a)
     {
          case 1:
               PerformCase1();
               break;
          case 2:
               PerformCase2();
               break;
     }
}
3) Move the declaration to before the switch statement and don't do initialization/declaration on the same line.
void MyFunction()
{
     int b, c;
     int a = GetValueOfA();
     switch(a)
     {
          case 1:
               b = 2;
               DoSomething(b);
               break;
          case 2:
               c = 3;
               DoSomethingElse(c);
               break;
     }
}
Another option would be to change the structure of the code such that the switch isn't necessary. In general, this would be my favored solution although in this specific case of legacy code, that would have involved more work than the budget allowed.

* Actually my notes say I also got a second error on the switch stating "transfer of control bypasses initialization of variable b" but I could not reproduce this one. Perhaps I simplified too much. Or perhaps it was a different version of the compiler. Or perhaps different option settings. Or something else entirely.