Incomplete Types in C

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.)

Advertisements

3 Responses to “Incomplete Types in C”


  1. 1 Jonathan Gilbert April 28, 2006 at 7:38 am

    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. 🙂

  2. 2 Mark April 29, 2006 at 12:18 pm

    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

  3. 3 Jonathan Gilbert May 1, 2006 at 6:37 am

    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”.


Comments are currently closed.




%d bloggers like this: