[comp.lang.c] incomplete pointers in prototypes

chris@mimsy.UUCP (Chris Torek) (03/12/89)

Someone else has already answered this (perhaps in comp.std.c), but it
bears repeating.

In article <1567@ubu.warwick.UUCP> geoff@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@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@mimsy.umd.edu	Path:	uunet!mimsy!chris