I have two structs in a header file, and they reference each other,
causing a compile error. Is there a standard way to deal with this?
typedef struct {
...
RtAction *actions;
} RtWidget;
typedef struct {
...
void (*action)(RtWidget *widget, XEvent *event);
} RtAction;
Yes. Stop using typedefs.
I am serious. They are not buying you much here, and -- by misleading
you -- are the cause of most of your grief. Typedefs do not define
types. "struct" keywords define types (when followed by "{", or
a tag and "{"). You have two type-defining "struct" keywords above.
You *must* use at least one structure tag here, because structure
tags can be declared as incomplete types and to be used in "forward
reference" fashion. Once you have the tags, you do not need the
typedefs.
struct RtAction; /* forward-declare the "RtAction" type */
struct RtWidget; /* forward-declare the "RtWidget" type */
struct RtWidget { /* re-declare, and now define, the "RtWidget" type */
...
struct RtAction *actions; /* refers to earlier forward-decl */
};
struct RtAction { /* define the "RtAction" type */
...
void (*action)(struct RtWidget *, XEvent *);
};
(Obviously you can eliminate at least one of the "forward
declarations". Since a definition is always also a declaration,
whichever actual definition comes first -- in this case
"struct RtWidget {" -- does not need a previous declaration.
In this *particular* case, you can actually drop both forward
declarations, but if you were to define the RtAction type
first, you would need the forward declaration for the RtWidget
type, due to somewhat silly, albeit "natural", scope rules.)
Now, if you have some reason you must insist on using those useless
"typedef" keywords just to avoid writing out C's "type" keyword
(i.e., "STRangely-spelled User-defined abstraCt Type" or "struct")
later, note that once you have forward-declared a structure, you
can then make a typedef alias for it:
struct forward_declared_type;
typedef struct forward_declared_type another_name_for_it;
/*
* now:
* struct forward_declared_type *p;
* and:
* another_name_for_it *q;
* declare p and q as pointers to the incomplete type
* whose "true name" is "forward_declared_type". The
* identifier "another_name_for_it" is just an alias for
* the real name.
*/
Of course, you can combine the two into one big mess (typedefs
always make a mess
) with the usual combo deal:
typedef struct forward_declared_type another_name_for_it;
The important thing is to use C's "type" keyword (i.e., "struct")
with a tag, so that you can repeat the name of the type when you
go to define it later:
struct forward_declared_type {
... contents ...
};
Without the tag, "struct {" starts defining a new, different,
unnamed type, instead of defining ("completing") the earlier
incomplete type.
Note that:
typedef struct { ... } alias;
defines a new type ("struct { ... }") that has no name and can
never be referred-to again, but then captures the "no-name" type
with the alias. For discussion purposes, we might refer to the
first no-name type as "$compiler_generated_name$00000", using
"$compiler_generated_name$00001" for the second, and so on. Then
we could say that:
typedef struct { int a; } alias00000;
typedef struct { int a; } alias00001;
defines two new structure types, $compiler_generated_name$00000
and $compiler_generated_name$00001, and gives these two names
(which cannot be spelled out in Standard C, at least, because
"$" is not a valid identifier) the aliases "alias00000" and
"alias00001". So even though:
struct $compiler_generated_name$00000 var_x;
does not work, we can then write:
alias00000 var_x;
Of course, this *looks like* two variable names, instead of C's
usual declaration syntax, but this is the big drawback of typedefs:
they muck up the syntax. Given:
void f(x);
is "x" a typedef-alias or a variable-name? Grab a C89 compiler
and find out:
% cat t.c
#ifdef TY
typedef struct tag { char *p; } x;
#endif
void f(x); /* line 4 */
void g(void) { f(3); } /* line 5 */
% cc -ansi -pedantic -c t.c
t.c:4: warning: parameter names (without types) in function declaration
% cc -DTY -ansi -pedantic -c t.c
t.c: In function `g':
t.c:5: incompatible type for argument 1 of `f'
%
The answer is: it is whichever one it is. If x has been declared
as a typedef-name, it is a type-name, and not a variable name; if
x has not been declared as a typedef-name, it is a variable name,
using C89's "implicit int" rule. (GCC produces a warning, which
is nice of it, since chances are good that this is not what the
programmer really meant; but this is not a "required diagnostic"
and some compilers do not warn.)
(This is fixed in C99, which has dropped the implicit int rule.)
The fact that typedefs muck up the syntax is the reason that those
who use typedef almost always invent some sort of spelling rules
to make the typedefs stand out. In POSIX, this is the "_t" suffix,
so that we have things like "uid_t" and "mode_t" and "pthread_t".
In much other software, one spells a typedef (and nothing else)
with an initial capital letter, so that:
GargleBlaster
is a typedef but:
mixPanGalacticDrink
is a function. (HHGttG was on last night.
) In Wind River's
conventions, typedefs are spelled in all-caps, so that they look
like macros (like Standard C's "FILE", in "FILE *stdin" and such).
In case it was not obvious, my preferred solution is "avoid
typedefs".
Of course, as with rules for code optimization,
it has some exceptions. Instead of "don't do it yet", it is
more a case of "for sufficiently complicated names, go ahead
and resort to a typedef, because it makes something clearer."
The classic situation is the one that comes up with the Standard
C "signal" function, which takes and returns a pointer to
a signal-handler:
/* version 1, without typedef */
void (*signal(int sig, void (*func)(int)))(int);
/* version 2, with a typedef */
typedef void (*SIG_FUNC_PTR)(int);
SIG_FUNC_PTR signal(int sig, SIG_FUNC_PTR func);
Version 2 is marginally easier to understand than Version 1,
primarily because it is now clear that signal() both takes and
returns a value of one particular (now-named) type: "signal takes
a new pointer-to-signal-handling-function, and returns the old
one".
Note that this does not affect structures, of course, because
user-defined abstract types ("struct"s) can and should have
names ("tags"):
struct zorg *change_evil(int which, struct zorg *new_evil);
Here "struct zorg" is the name of the type, just like "int" is the
name of a type; it just happens to be spelled with the keyword for
"user-defined-type-name" followed by the user-defined type-name,
instead of a single word. This is a *good thing*: no special
typographic convention is required to alert the user (and compiler)
to the fact that this *is* a user-defined type-name. We do not
have to write "struct zorg_t" (_t suffix) or "struct Zorg"
(initial-capital) or "struct ZORG" (all-caps), because the "struct"
keyword does the trick.
Executive summary: "typedef"s bad. Avoid.