This question puts me in a stupor.
I didn't know that I couldn't just declare a variable with an incomplete type without initialization.
Here is the code:
#include <stdio.h>
int n[];
int main(void)
{
int m[];
void p;
return 0;
}
That's what the compiler gives me:
1.c: In function 'main':
1.c:5:9: error: array size missing in 'm'
5 | int m[];
| ^
1.c:6:10: error: variable or field 'p' declared void
6 | void p;
| ^
1.c: At top level:
1.c:2:5: warning: array 'n' assumed to have one element
2 | int n[];
I use Visual Studio Code version 1.84.2 and my gcc version is 13.2.0
It does not throw an error for declaring n, but it throws an error when declaring m and P.Why is that?
Why can't I declare a variable inside a block?
Maybe this is the reason?(n1256 6.7.2.3 ):
111)An incomplete type may only by used when the size of an object of that type is not needed. It is not needed, for example, when a typedef name is declared to be a specifier for a structure or union, or when a pointer to or a function returning a structure or union is being declared. (See incomplete types in 6.2.5.) The specification has to be complete before such a function is called or defined.
Why can't I declare a variable with an incomplete type?
Under some circumstances, you can. And in your example, you successfully do, for array n
.
First, the definitions, from C23 6.2.5/1:
At various points within a translation unit an object type can be incomplete (lacking sufficient information to determine the size of objects of that type) or complete (having sufficient information).
That slightly undersells, however. Structure and union types are incomplete when their members are not (yet) specified, and that does mean that their sizes are unknown, but there are places where the spec invokes structure type completeness specifically for member declaredness, with structure size being a side issue.
Footnotes 28 and 29 to that definition clarify:
An incomplete type can only be used when the size of an object of that type is not needed. It is not needed, for example, when a typedef name is declared to be a specifier for a structure or union, or when a pointer to or a function returning a structure or union is being declared. The specification has to be complete before such a function is called or defined.
A type can be incomplete or complete throughout an entire translation unit, or it can change states at different points within a translation unit.
The main incomplete types are
void
, which cannot be completedWith respect to completing incomplete types, however, it is important to bear in in mind that:
If an identifier for an object that does not have function prototype scope is declared with no linkage, the type for the object shall be complete by the end of its declarator, or by the end of its init-declarator if it has an initializer.
(C23 6.7.1/6)
That's the language-lawyer reason why your compiler rejects the declarations of m
and p
in your main()
, even though the program never attempts to access those objects.
It's also relevant that in C, some declarations are also definitions. A definition of an object identifier is a declaration that causes storage to be reserved for the designated object (C23 6.7.1/7). And some other declarations are a weird thing called a tentative definition:
A declaration of an identifier for an object that has file scope without an initializer, and without the storage-class specifier
extern
orthread_local
(C23 6.9.3/2)
The declaration of n
in your example program is one of these. The spec goes on to say of them:
If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier with an empty initializer and a type determined as follows:
- if the composite type as of the end of the translation unit is an array of unknown size, then an array of size one with the composite element type;
- otherwise, the composite type at the end of the translation unit.
That is, as your program is interpreted as if it contained an additional definition of the form
int n[1] = {}; // an array of size one with the composite element type
, which completes the type of n
and provides a definition for it with use of empty initialization. That causes all elements' initial valeus to be 0.
That is, it's as if the array were declared to have one element. Your compiler seems to be using that interpretation, as it's exactly what its warning (not error!) about the situation tells you.
Every version of the C spec from C99 onward has contained an example of exactly this case.
Additionally, because the example program does not attempt to access n
, the program does not need to define it. The provided tentative definition could be turned into a pure declaration by adding the extern
storage class specifier:
extern int n[]; // Now only a declaration
That's also what you would want to do if the program did access n
, but it was defined in a different translation unit. And that would work inside main()
, too, though block-scope declarations of identifiers with external linkage are widely considered to be poor style.