struct interdependencies

R

Russell Shaw

Hi,

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;
 
R

Robert Gamble

Hi,

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;

How about this:

typedef struct RtAction * RtAction;
typedef struct RtWidget * RtWidget;

struct RtWidget {
...
RtAction actions;
};

struct RtAction {
...
void (*action)(RtWidget widget, XEvent *event);
};

Rob Gamble
 
C

Chris Torek

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

Russell Shaw

Robert said:
How about this:

typedef struct RtAction * RtAction;
typedef struct RtWidget * RtWidget;

struct RtWidget {
...
RtAction actions;
};

struct RtAction {
...
void (*action)(RtWidget widget, XEvent *event);
};

Rob Gamble

That works well, tho i used:

typedef struct RtAction RtAction;
typedef struct RtWidget RtWidget;
 
E

Emmanuel Delahaye

Russell Shaw wrote on 17/04/05 :
Hi,

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, as long as you deal with pointers...

typedef struct RtAction RtAction;

typedef struct {
...
RtAction *actions;
} RtWidget;

struct RtAction {
...
void (*action)(RtWidget *widget, XEvent *event);
} RtAction;

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

I once asked an expert COBOL programmer, how to
declare local variables in COBOL, the reply was:
"what is a local variable?"
 
K

Keith Thompson

Russell Shaw said:
That works well, tho i used:

typedef struct RtAction RtAction;
typedef struct RtWidget RtWidget;

Good for you. I was just about to complain that Robert's solution,
though valid, is a bit obfuscated. He has the name RtAction referring
both to a type (the struct) and to a pointer to that type (the
typedef). They're different things, so they should have different
names.

Hiding a pointer type in a typedef is seldom a good idea unless you
really want to hide it (i.e., you don't want the code that uses the
type to know or care that it's a pointer).

Chris Torek's post about avoiding typedefs is also good advice.
 
K

Keith Thompson

Chris Torek said:
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, youn
can then make a typedef alias for it:
[...]

I agree with what you wrote (though of course one must sometimes deal
with code written by people who do like typedefs). Just one quibble.
A struct declaration does create a type, but it's only one of several
kinds of types (though the most flexible kind). Structs, unions,
enums, pointers, and arrays are all types that can be created by
declarations. What you wrote above implies that a struct declaration
is *the* way to create a type in C.
 
C

Christian Bau

Robert Gamble said:
How about this:

typedef struct RtAction * RtAction;
typedef struct RtWidget * RtWidget;

This might come up as an interview question.

What's wrong with the following code?

typedef struct RtAction * RtAction;
typedef struct RtWidget * RtWidget;
 
R

Robert Gamble

Good for you. I was just about to complain that Robert's solution,
though valid, is a bit obfuscated. He has the name RtAction referring
both to a type (the struct) and to a pointer to that type (the typedef).
They're different things, so they should have different names.

Hiding a pointer type in a typedef is seldom a good idea unless you
really want to hide it (i.e., you don't want the code that uses the type
to know or care that it's a pointer).

Chris Torek's post about avoiding typedefs is also good advice.

Agreed on all 3 points, I was trying to demonstrate the concept but should
have changed the names for clarification, thanks for pointing this out.

Rob Gamble
 
O

Old Wolf

Chris said:
Stop using typedefs.

One problem I find when using struct tags rather than typedefs:

struct gargleblaster
{
int whatever;
};

void foo(struct garbleglaster *ptr);

will raise no compiler errors, until later on when I try to
define foo() and assign ptr, then it will tell me that ptr's
type is incompatible with what I'm assigning it to. Whereas:

typedef struct { int whatever; } hyperspacebypass;
void bar(hypespacebypass *ptr);

will cause a compiler error right then and there.
 
R

Russell Shaw

Old said:
One problem I find when using struct tags rather than typedefs:

struct gargleblaster
{
int whatever;
};

void foo(struct garbleglaster *ptr);

will raise no compiler errors, until later on when I try to
define foo() and assign ptr, then it will tell me that ptr's
type is incompatible with what I'm assigning it to. Whereas:

typedef struct { int whatever; } hyperspacebypass;
void bar(hypespacebypass *ptr);

will cause a compiler error right then and there.

Can't see why the latter should be in error. I was always using the
same thing in gcc.
 
K

Keith Thompson

Russell Shaw said:
Can't see why the latter should be in error. I was always using the
same thing in gcc.

It's a (deliberate) typo: "hyperspacebypass" vs. "hypespacebypass".
 
C

Chris Croughton

Hiding a pointer type in a typedef is seldom a good idea unless you
really want to hide it (i.e., you don't want the code that uses the
type to know or care that it's a pointer).

It's something the authors of zlib did, with fatal results.

typedef void *voidp; /* looks harmless enough */

extern ... gzwrite(..., const voidp buf, ...);

func(const void* buffer)
{
gzwrite(..., buffer, ...); /* oops! */
}

I passed a pointer to constant void to gzwrite which expected a const
pointer to void, and there was nothing (short of putting in a (voidp)
cast) I could do to make it compile with gcc 3.x. gcc 2.95.x didn't
detect any inconsistency...

(The latest version of zlib doesn't do that -- it defines voidpc as
typedef const void *voidpc!)
Chris Torek's post about avoiding typedefs is also good advice.

I would disagree about avoiding them totally, but certainly the practice
of making "pointer types" is dangerous...

Chris C
 
C

Chris Croughton

Can't see why the latter should be in error. I was always using the
same thing in gcc.

Did you spot the error in the first example using struct? The error is
the same (mistyped identifier), but in the first case the compiler will
just say "OK, I'm using a structure with incomplete type, no problem"
until you try to access something in that structure or pass a pointer to
a structure with the correct name to the function. And then it can take
ages to spot cause of the error, because it's subtle and removed from
the place of the actual error. That's why using the typedef is better,
the compiler will spot immediately "hypespacebypass is not a type".

Chris C
 
R

Russell Shaw

Chris said:
Did you spot the error in the first example using struct? The error is
the same (mistyped identifier), but in the first case the compiler will
just say "OK, I'm using a structure with incomplete type, no problem"
until you try to access something in that structure or pass a pointer to
a structure with the correct name to the function. And then it can take
ages to spot cause of the error, because it's subtle and removed from
the place of the actual error. That's why using the typedef is better,
the compiler will spot immediately "hypespacebypass is not a type".

I have an annoying problem of reading by patterns, and if the patterns
look similar, i don't see the problem even after reading multiple times.
If i *know* the error is in that location, i start reading letter by letter
(i got aspergers a bit).
 
C

Chris Croughton

I have an annoying problem of reading by patterns, and if the patterns
look similar, i don't see the problem even after reading multiple times.
If i *know* the error is in that location, i start reading letter by letter
(i got aspergers a bit).

In this case your 'problem'[1] illustrated the reason for using the
typedef form, because that will show you the line where the error is.
If the error is shown only when a field is accessed or the function is
called with the correct pointer, which might be several hundred lines
further on (and not even in the same file, the prototype might be in a
header). (I also didn't spot it right away, so I shoved it into a file
and gcc pointed it out. I've also got a bit of (undiagnosed) Aspergers,
and some dyslexia which has the 'i18n' problem, I see the start and end
of a word and fill in the rest...)

[1] I think everyone does that to an extent, does anyone actually read
through all of the letters of every word they see? I only do it if I
think something's wrong.

Chris C
 
M

Michael Wojcik

One problem I find when using struct tags rather than typedefs:

struct gargleblaster
{
int whatever;
};

void foo(struct garbleglaster *ptr);

will raise no compiler errors,

It produces a diagnostic from at least one implementation I've used
(I think it was one of Sun's C compilers), warning that I'd just
declared an incomplete struct type in prototype scope, which was
probably not what I wanted to do. A type declaration in prototype
scope is useless, AFAICT, so that's a reasonable warning.
until later on when I try to
define foo() and assign ptr, then it will tell me that ptr's
type is incompatible with what I'm assigning it to.

Still, isn't that a pretty clear diagnosis of the problem?
 
C

Christian Bau

It produces a diagnostic from at least one implementation I've used
(I think it was one of Sun's C compilers), warning that I'd just
declared an incomplete struct type in prototype scope, which was
probably not what I wanted to do. A type declaration in prototype
scope is useless, AFAICT, so that's a reasonable warning.

Not really reasonable. Declaring a complete struct type in prototype
code would be nonsense, because that declaration wouldn't be usable
outside. But declaring a pointer to an incomplete struct is useful.
There are even practical uses if the complete struct is never defined.
 
M

Michael Wojcik

Not really reasonable. Declaring a complete struct type in prototype
code would be nonsense,

It's not nonsense; it's well-defined. It's just useless.
because that declaration wouldn't be usable
outside. But declaring a pointer to an incomplete struct is useful.
There are even practical uses if the complete struct is never defined.

Indeed it is, and that doesn't involve a declaration of an incomplete
struct type in prototype scope, provided the incomplete struct type
has already been declared. The implementation in question does not
issue a diagnostic for that, and I wasn't suggesting any implementa-
tion should. It's a completely different situation.

Note that if no declaration (complete or incomplete) of the struct
type is in scope when the struct pointer type is used in the
prototype, that still constitutes a declaration at prototype scope,
which is *not* compatible with any subsequent declaration of a
struct type with the same tag.

That is,

struct foo;
void bar(struct foo *);
void baz(struct foo *);

correctly declares bar and baz as taking pointers to the same
incomplete structure, but without a prior file-scope declaration of
struct foo, their parameters would be of different type - and it
would be a type that could not be replicated elsewhere. (The only
place it could be used again is in the same prototype, which would
still be pointless.)

Prototype-scope declarations of struct types, complete or incomplete,
are never useful, regardless of whether they're then used to declare
pointer types.

If you think a prototype-scope declaration of a struct type is ever
useful, feel free to demonstrate how.

--
Michael Wojcik (e-mail address removed)

Thus, the black lie, issuing from his base throat, becomes a boomerang to
his hand, and he is hoist by his own petard, and finds himself a marked man.
-- attributed to a "small-town newspaper editor in Wisconsin"
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,163
Messages
2,570,897
Members
47,434
Latest member
TobiasLoan

Latest Threads

Top