Why the compiler won’t let you declare a variable immediately after a case
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
)
- labeled statement
- compound statement
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
)
- labeled statement
- compound statement
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.])
February 20th, 2009 at 12:37:13
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!
February 20th, 2009 at 16:33:21
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…
February 20th, 2009 at 18:09:39
My workaround is to put the case label on an empty statement:
Looks ugly, but it works.
February 20th, 2009 at 22:04:14
It makes sense, in a very stupid way, if you consider labeled statements /outside/ of a
switch
. You can’t label a declaration with agoto
-style label because there’s no statement togoto
.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*February 21st, 2009 at 07:13:43
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
,do
…while
, andswitch
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.