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