I happened across the following bit of C code at work today. It attempts to allocate storage for an incomplete type:
/* foo.c */
struct foo bar;
struct foo { int x; } bap;
I was surprised to find that this code compiles cleanly with GCC 3.4, Intel 8.1, and Comeau C/C++ 4.3.3 (apparently reserving 4 bytes for both bar
and bap
). Microsoft Visual C 8.0, however, produces the following error:
foo.c(1) : error C2079: 'bar' uses undefined struct 'foo'
MSVC’s error seems pretty reasonable to me. The primary characteristic of a C definition is that it reserves storage. If you don’t yet know the size of foo
, you can’t reserve storage.
I’ve got a copy of the C language spec. Can someone point me to a relevant passage?
Update:
Upon review, it seems that this is indeed legal C. The Microsoft compiler should probably accept it. (Thanks to Jonathan Caves for looking at this.)
I found a draft of some version of the C standard at the following URL:
http://www.vmunix.com/~gabor/c/draft.html
..and while scanning through it quickly, found the following:
6.5:
[#5] A declaration specifies the interpretation and
attributes of a set of identifiers. A definition of an
identifier is a declaration for that identifier that:
– for an object, causes storage to be reserved for that
object;
[#7] If an identifier for an object 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.
6.5.2.3:
[#3] All declarations of structure, union, or enumerated
types that have the same scope and use the same tag declare
the same type. The type is incomplete94 until the closing
brace of the list defining the content, and complete
thereafter.
94. 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.1.2.5.) The
specification shall be complete before such a function
is called or defined.
From addendum K.2: Undefined behaviour
– An identifier for an object is declared with no linkage and
the type of the object is incomplete after its declarator,
or after its init-declarator if it has an initializer (6.5).
These would tend to suggest that Microsoft’s compiler is showing compliant behaviour in rejecting the declaration of “bar” with an incomplete type. However, following up a reference not too far below the point of undefined behaviour shown above:
– An identifier for an object with internal linkage and an
incomplete type is declared with a tentative definition
(6.7.2).
.. it would seem that it is in fact valid:
6.7.2:
[#2] A declaration of an identifier for an object that has
file scope without an initializer, and without a storage-
class specifier or with the storage-class specifier static,
constitutes a tentative definition. 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 the composite type as of the end of
the translation unit, with an initializer equal to 0.
Basically, if an identifier is declared:
* With file scope (check)
* Without an initializer (check)
* With no storage class specifier (check)
* One or more times (check)
* With no external definition within the translation unit (check)
..then the identifier’s type is treated as it would be were the declaration at the very end of the file. As a bonus, it is also given an initualizer of “= {0};”. So I’d have to say GCC, Intel, Comeau and anyone else who compiles the fragment you gave got it right, and Microsoft didn’t read section 6.7.2 closely enough. 🙂
Jonathan,
Thanks. I’ve got to compare that with the official copy of the C standard that I have at work. Also, I might bug one of the MSVC front-end guys, to get their opinion.
I’m probably playing fast-and-loose with the term “scope”, but I thought file-scope in C was based on the static qualifier.
/* foo.c */
static int i; /* visible only from foo.c */
// foo.cpp
namespace {
int i; // file-scope the C++ way
}
-Mark
The standard I read seems to refer to the “static” modifier as part of the symbol’s linkage. Scoping refers to the namespaces affected by having that declaration at that point in that file. Thus, with or without “static”, as that particular declaration affects only that file (and everywhere within that file), it is called “file scope”.
The standard refers to this:
int i;
..as “external linkage”, and this:
static int i;
..as “internal linkage”.