incomplete pointers in prototypes (was Recursive #includes)
Chris Torek
chris at mimsy.UUCP
Sun Mar 12 17:24:44 AEST 1989
Someone else has already answered this (perhaps in comp.std.c), but it
bears repeating.
In article <1567 at ubu.warwick.UUCP> geoff at cs.warwick.ac.uk (Geoff Rimmer)
writes:
[line 1]>static void function (struct egg * a);
[global definition of struct egg on line 3]
[line 5]>static void function (struct egg * a)
[line 5]>{
>--geoff at emerald% gcc -ansi -pedantic str.c
>str.c:1: warning: `struct egg' declared inside parameter list
>str.c: In function function:
>str.c:6: conflicting types for `function'
>str.c:1: previous declaration of `function'
The problem, which gcc has in fact noted (with a warning), is that
lines 1 and 5 use different instances of `pointer to struct egg'.
What is going on here is that a prototype provides a new environment,
much like an opening `{'. When we look at this code:
struct egg { long a, z; };
void fn() {
struct egg { char alpha, omega; };
...
}
we can see that there are two different kinds of `struct egg's. The
kind outside `fn' has two long integers `a' and `z'; the kind inside
`fn' has two characters `alpha' and `omega'. The alpha/omega egg is
not type-compatible with the a/z egg.
With prototypes, the opening `(' of a function declaration also provides
a new environment, so we could write, for instance,
struct egg { long a, z; };
void eat(struct egg { char alpha, omega; } *p);
and `eat' would take a different kind of `struct egg'. Now things get
a bit weird.
Most languages provide one of two kinds of type compatibility rules.
One, `name compatibility', means that objects are type-compatible if
the have the same `name'. `name' here does not mean the spelling of
the structure name, but rather an internal compiler-generated tag which
is unique for each new structure type declaration. The other kind of
rule, `structural compatibility', means that objects are type-
compatible as long as they consist of the same basic sub-objects. A
`struct foo' can be structurally compatible with a `struct bar' if both
contain two `int's, one `char', and one `pointer to function returning
void', in that order. Name compatibility is easier to test, so it tends
to be used more often.
C uses name-compatibility, except where it cannot (across modules).
So what is happening when you write
1: void fn(struct egg *p);
2: struct egg { <contents-here> };
3: main() { struct egg e; fn(&e); }
is that `fn' requires an argument of type `pointer to struct egg<0>'
but main is providing one of type `pointer to struct egg<1>'. Line 1
declares a new `struct egg' type because there is no existing `struct
egg' type around. It gets tag 0, then the scope goes away, so there is
still no `struct egg' type around when line 2 is compiled. It declares
another new one and gets tag 1.
The solution to the mess is simple: provide a `struct egg' before the
prototype declaration for fn(), so that the compiler can use the
existing internal tag:
1: struct egg;
2: void fn(struct egg *p);
3: struct egg { <contents-here> };
4: main() { struct egg e; fn(&e); }
The sneaky thing happening here is that line 1 declares an incomplete
`struct egg' type, so it does not `use up' tag egg<0>. When line 3
comes along it gets tag egg<0> again. (Actually, the same thing
happens even if line 1 does not declare an incomplete type, but it is
an error to attempt to redefine an existing completed tag. The error
does not occur if one is at a new scoping level since then the new
declaration simply covers the existing one until that level is closed.
Anyway, the real trick is that line 2 needs to find some `egg' in the
structure symbol table, so that it will not put a new one there.)
--
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain: chris at mimsy.umd.edu Path: uunet!mimsy!chris
More information about the Comp.lang.c
mailing list