Why the compiler won’t let you declare a variable immediately after a case

2009-02-20 11:01:38 -08:00

Consider this code:

enum { foo, bar, baz } my_var;
switch (my_var) {
    case foo:
        int foo_alpha; //Line 7
        int foo_beta;
        break;

    case bar:
        my_var = baz;
    case baz:
        printf("Bar or baz encountered\n");
        break;
}

Try to compile it as C99.

Here’s what GCC says:

test.c: In function ‘main’:
test.c:7: error: syntax error before ‘int’

What!

First off, why not? What is invalid about this declaration statement?

Second, why is foo_alpha invalid and not foo_beta?

There are several definitions in the C99 specification that come together to cause this problem.

The first is that there is no such thing as a declaration statement, because declarations are not statements. See §6.7 and §6.8; note that neither definition includes the other. In the language that is C99, declarations and statements are separate concepts.

The second is the definition of a compound statement. The definition of a switch statement (which is part of §6.8.4) is:

switch ( expression ) statement

If you go back up to §6.8, you’ll see that another possible kind of statement is a compound statement, for which §6.8.2 gives this definition:

compound-statement:
  • {   block-item-listopt   }
block-item-list:
  • block-item
  • block-item-list   block-item
block-item:
  • declaration
  • statement

So a declaration is not a statement, a compound statement can contain declarations and/or statements, and a switch statement is a prefix upon (usually) a compound statement.

Now, the kicker. Read the relevant definition of a labeled statement from §6.8.1:

case   constant-expression   :   statement

Statement. Not block-item. Not declaration. Statements only.

So this is what the compiler sees in valid code (with a declaration not following a case label):

  • selection statement (switch)
    • compound statement
      • labeled statement
        • statement
      • statement
      • statement
      • declaration
      • statement
      • jump statement (break)
      • labeled statement
      • statement
      • jump statement (break)
      • labeled statement
      • statement
      • jump statement (break)

Now, consider how the compiler sees my code above:

  • selection statement (switch)
    • compound statement
      • labeled statement
        • declaration — wait, this isn’t a statement! ERROR
      • declaration — also a kind of block-item, so it’s perfectly valid here
      • jump statement (break)
      • labeled statement
      • statement
      • labeled statement
      • statement
      • jump statement (break)

I hope this makes clear that this isn’t a compiler bug; the C99 language really does work this way.

(One possible solution would be to make a declaration a kind of statement, but I don’t know what other ramifications that might have. [UPDATE 11:36: Jeff Johnson tells us why not.])

5 Responses to “Why the compiler won’t let you declare a variable immediately after a case”

  1. Mike Ash Says:

    An extremely simple workaround is to just use braces with your case statements:


    case foo:
    {
        int foo_alpha;
        ...
    }
    break;

    Just be sure you don’t forget the break at the end. The braces aren’t enough for that!

  2. ssp Says:

    Your logic here may be impeccable – I couldn’t take the pain of reading all the details, but I distinctly remember running into this dumbfuckery of C a few times and ending up either having defined my variables more globally than they need be or, in later iterations being amazed that the branch with some (coincidental) braces worked.

    So it’s not a compiler bug, it’s merely a C bug. Wonderful…

  3. Peter Hosey Says:

    My workaround is to put the case label on an empty statement:

    case foo:;
    	int foo_alpha;

    Looks ugly, but it works.

  4. Jordy/Jediknil Says:

    It makes sense, in a very stupid way, if you consider labeled statements /outside/ of a switch. You can’t label a declaration with a goto-style label because there’s no statement to goto.

    But given the common use of switch statements, this is silly. Java (among other languages, maybe C++ too, I can’t remember) does the Right Thing by syntactically making declarations statements, and making sure the variables they’re declaring are usable in the rest of the block.

    C’s switch is ugly anyways. And no one can agree how to indent it. *grin*

  5. Peter Hosey Says:

    JediKnil: Actually, you might very well want to jump to the top of a block, where there may be declarations.

    And did you read Jeff Johnson’s tweet, which I linked to? There’s a very simple reason why you can’t make declarations statements. The same reason applies to for, while, dowhile, and switch itself. I, for one, think disallowing “if (foo) int foo_alpha;” is a valuable syntax error for newcomers to the language.

    A better solution may be to change the definition of labeled-statement to so that a block-item, not only a statement, follows the label. But, again, I haven’t thought through any ramifications of that.

Leave a Reply

Do not delete the second sentence.