93
if (1) int a = 2;

This line of code is valid C++ code (it compiles at the very least) yet invalid C code (doesn't compile). I know there are differences between the languages but this one was unexpected.

I always thought the grammar was

if (expr) statement

but this would make it valid in both.

My questions are:

  1. Why doesn't this compile in C?
  2. Why does this difference exist?
user438383
  • 5,716
  • 8
  • 28
  • 43
Tom Hickson
  • 1,095
  • 7
  • 12
  • 15
    Which compiler? What is the error? – lakeweb Jul 02 '22 at 23:19
  • 1
    Could you send what error are you getting while compiling, and as lakeweb said, what compiler are you using – Saurav Chittal Jul 02 '22 at 23:22
  • 22
    *why does this difference exist?* I would answer: because C and C++ are different programming languages. – 273K Jul 02 '22 at 23:27
  • See bolov's answer below for a more meaningful reason as to why the difference exists. – Alexander Guyer Jul 02 '22 at 23:35
  • As answers below have pointed out, this happens whenever a _statement_ is expected, hence not limited to `if`. This includes "the line after" `for`, `while`, `switch` and labels, to name a few. – YiFei Jul 02 '22 at 23:48
  • 4
    Another place that shows the difference is that you can't add a label to a declaration in C. – Jonathan Leffler Jul 03 '22 at 02:51
  • @JonathanLeffler, just tried and I seem to be able to add a label or case before a declaration just fine in C99 and C11. – Klaas van Aarsen Jul 04 '22 at 12:57
  • 2
    @KlaasvanAarsen: Testing, especially with GCC, is not informative. [§6.8.1 Labelled statements](http://port70.net/~nsz/c/c11/n1570.html#6.8.1) specifies that only statements can be labelled and [§6.8.2 Compound statement](http://port70.net/~nsz/c/c11/n1570.html#6.8.2) specifies that block statements consist of sequences of declarations and statements. The grammar for Standard C does not allow labels on declarations. C++ does allow them; some C compilers may allow it, but it is an extension over (current) Standard C. C2x may change that. – Jonathan Leffler Jul 04 '22 at 13:05
  • `if (1) int a = 2;` does this just declare and initialize a variable which immediately goes out of scope? – Lorraine Jul 06 '22 at 08:29

4 Answers4

90

This is a subtle and important difference between C and C++. In C++ any statement may be a declaration-statement. In C, there is no such thing as a declaration-statement; instead, a declaration can appear instead of a statement within any compound-statement.

From the C grammar (C17 spec):

compound-statement: "{" block-item-listopt "}"
block-item-list: block-item | block-item-list block-item
block-item: declaration | statement

From the C++ grammar (C++14 spec):

compound-statement: "{" statement-seqopt "}"
statement-seq: statement | statement-seq statement
statement: ... | declaration-statement | ...

It is not clear why this difference exists, it is just the way the languages evolved. The C++ syntax dates all the way back to (at least) C++85. The C syntax was introduced sometime between C89 and C99 (in C89, declarations had to be at the beginning of a block)


In the original 85 and 89 versions of C++, the scope of a variable defined in a declaration-statement was "until the end of the enclosing block"). So a declaration in an if like this would not immediately go out of scope (as it does in more recent versions) and instead would be in scope for statements following the if in the same block scope. This could lead to problems with accessing uninitialized data when the condition is false. Worse, if the var had a non-trivial destructor, that would be called when the scope ended, even if it had never been initialized! I suspect trying to avoid these kinds of problems is what led to C adopting a different syntax.

Farzad Karimi
  • 770
  • 1
  • 12
  • 31
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • 9
    Imho, C moving to the C++ definition would simplify the language by removing gotchas, in addition to removing accidental differences. Just a shame they didn't do it in C99 when following common practice and allowing statements and definitions to mix. – Deduplicator Jul 03 '22 at 20:57
75

Chris's answer (and others) shows how the grammar is different.

I want to point out that if (1) int a = 2; doesn't make sense in C but it does in C++. Since we don't have a block but just 1 statement/declaration there is no possible further use of the variable declared (it goes out of scope immediately). In C this would make no sense to allow, but in C++ constructors and destructors can have side effects, so defining and initializing a variable that goes out of scope immediately can be useful and must be allowed.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • 4
    However, the address could be used. `int a=foo(&a);`. – tstanisl Jul 03 '22 at 06:52
  • 1
    @tstanisl But what's a possible use case for that? All I can think of is if `foo()` requires a pointer to an output location and you don't need that result. – Barmar Jul 03 '22 at 19:45
  • @Barmar, yes, but `foo()` may have some side effects and it may require some external storage to do the job. Though using compound literal `foo(&(int){0})` may be a more appropriate solution. – tstanisl Jul 03 '22 at 20:26
  • It's equally useful/useless in either language, being lawful in only one is just an accidental difference. – Deduplicator Jul 03 '22 at 20:29
  • 4
    @Deduplicator Good point. Just because you *can* use the variable just for its constructor/destructor side effect doesn't mean you *should*. Hiding semantics there makes for very obscure coding. – Barmar Jul 03 '22 at 20:39
  • 3
    @tstanisl However, the `foo(&(int){0})` syntax fully initializes the object before passing its address, while the `int a = foo(&a);` syntax does not touch the memory of `a` until after `foo()` has returned. Another slight and subtle difference that could conceivably change the semantics of the program. – cmaster - reinstate monica Jul 03 '22 at 22:02
  • 2
    @cmaster-reinstatemonica: If you ever actually wanted to do this in C, it's trivial to wrap `{}` around the declaration that includes a function call. Then it becomes legal even in C89. It is a weird quirk of the grammar that that's needed, though, but it doesn't really need fixing since there's always a workaround that's equivalent, even for optimization purposes, in (all, I hope) real-world compilers. – Peter Cordes Jul 04 '22 at 07:36
  • @PeterCordes I never intended to abuse C in such a way. I merely commented on the fact that the two variants do exhibit different behavior, i.e. that they are not equivalent. That does not mean that I would ever rely on this behavior difference. Code should be as simple as possible, after all. – cmaster - reinstate monica Jul 04 '22 at 08:11
28

It's because C and C++ define statement differently.

In C, declarations are not classified as statements. A C compound statement consists of an opening {, an option list of block-items, and a closing }, where a block-item either a declaration or a statement. (This was changed in C99, when C added the ability to mix declarations and statements within a block.)

In C++, a declaration is classified as a statement (but only if it's inside a compound statement). This allows a simpler definition of a compound-statement: it's a {, followed by an optional list of statements, followed by a }.

The difference doesn't have much practical effect; it's always possible to work around it. One effect is that it's legal in C++ for a declaration immediately follow a case label, but in C it's not legal.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
15

In C, a declaration and a statement are distinct entities.

In C++, a a subset of declarations called a block-declaration is a type of statement, specifically it is a declaration-statement. These include simple declarations like int a=2.

dbush
  • 205,898
  • 23
  • 218
  • 273