joe.ds
joe.ds

Reputation: 125

C Structure typedef with Forward Declarations

I was using typedef for structures everywhere in my application. I then started to refactor into several header files when it started to get clunky. I noticed I needed to forward declare Object, and Klass. Well, to my surprise, I couldn't forward declare Object, or Klass. This is because, as you can see in the Object and Klass structure, I'm using a typedef of Object and Klass.

//Klass.h

typedef struct Klass Klass_t;

struct Klass
{
    void (*_initialize)(Object_t* object, Klass_t* klass);
};

//Object.h

typedef struct Object Object_t;

struct Object
{
    Klass_t* _klass;
};

At first, using typedef was great. But trying to forward declare Object:

struct Object_t;

Doesn't work as I would need to rewrite by function declarations as:

void (*_initialize)(struct Object_t* object, Klass_t* klass);

So I decided to just typedef Object inside the Klass.h header file:

typedef struct Object Object_t;

Well when all header files are included into my Main.c file, it complians:

Object.h:5: error: redefinition of typedef 'Object_t'

So, I then decided to just drop all struct typedefs and explicity declare my structures.

Is there a way to typedef a structure and forward declare in another file without explicitly using struct Object?

I want to keep structure typedefs inside the header file where the structure is declared. If I have to group all typedefs inside one header file then I would rather not use typedef at all. Anyways, thanks for your time.

Upvotes: 4

Views: 11758

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 755064

Remember that a typedef is just an alternative name for a type. There's a reason the Linux kernel doesn't use typedefs for structure types, and you're demonstrating it.

There are a couple of ways around your problem. I note that C11 does allow multiple occurrences of the same typedef, but I'm assuming you're stuck with an older compiler that does not support that.

TL;DR

Even though the Linux kernel doesn't use typedefs, I usually do use typedefs, but I mostly avoid mutually referential structure types (I can't think of any code where I used such a type). And, like Jens Gustedt notes in his answer, I almost invariably use the notations:

typedef struct SomeTag SomeTag;

so that the type name and the structure tag are the same (they're in different namespaces). This operation is not necessary in C++; when you define struct SomeTag or class SomeTag, the name SomeTag becomes a type name without the need for an explicit typedef (though a typedef does no harm other than revealing that the author is more experienced in C than C++, or the code originated as C code).

I also observe that names starting with an underscore are best treated as 'reserved for the implementation'. The rules are a little more complex than that, but you run a risk of the implementation usurping your names — and being within its rights to usurp your names — when you use names starting with an underscore, so don't. Likewise, POSIX reserves type names ending _t for the implementation if you include any POSIX headers (such as <stdio.h> when you aren't compiling in strict Standard C only mode). Avoid creating such names; they'll hurt you sooner or later. (I've not fixed the code below to deal with either of these issues: caveat emptor!).

In the code fragments below, I'm mostly ignoring the code that prevents multiple inclusions (but you should have it in your code).

Extra header

typedefs.h:

typedef struct Object Object_t;
typedef struct Klass  Klass_t;

klass.h

#include "typedefs.h"

struct Klass
{
    void (*_initialize)(Object_t *object, Klass_t *klass);
};

object.h

#include "typedefs.h"

struct Object
{
    Klass_t *_klass;
};

This works because the two type names Klass_t and Object_t are declared before they're used.

Use struct Object in prototype

klass.h

typedef struct Klass Klass_t;
struct Object;

struct Klass
{
    void (*_initialize)(struct Object *object, Klass_t *klass);
};

Or, for consistency, it might even use:

    void (*_initialize)(struct Object *object, struct Klass *klass);

object.h

#include "klass.h"

struct Object
{
    Klass_t *_klass;
};

This works because (within broad limits — basically, if the types are defined at file scope, not inside a function) struct Object always refers to the same type, regardless of whether all the details are fully defined yet.

GCC 4.8.2

Under all of -std=c89, -std=c99 and -std=c11, GCC 4.8.2 accepts replicated typedefs, as in the code below. It requires -std=c89 -pedantic or -std=c99 -pedantic to get errors about the repeated typedefs.

Even without the -pedantic option, GCC 4.5.2 rejects this code; however, GCC 4.6.0 and later versions accept it without the -pedantic option.

klass.h

#ifndef KLASS_H_INCLUDED
#define KLASS_H_INCLUDED

typedef struct Klass Klass_t;
typedef struct Object Object_t;

struct Klass
{
    void (*_initialize)(Object_t *object, Klass_t *klass);
};

#endif /* KLASS_H_INCLUDED */

object.h

#ifndef OBJECT_H_INCLUDED
#define OBJECT_H_INCLUDED

typedef struct Klass Klass_t;
typedef struct Object Object_t;

struct Object
{
    Klass_t *klass;
};

#endif /* OBJECT_H_INCLUDED */

consumer.c

#include "klass.h"
#include "object.h"

Klass_t k;
Object_t o;

You'll have to decide whether that's a risk you're willing to take for your code — how important is portability, and to which versions of C (and which C compilers) must it be portable.

Upvotes: 4

Jens Gustedt
Jens Gustedt

Reputation: 78993

You are missing the struct keyword. It should be

typedef struct Object Object_t;

Forward declaration in that way (but see below) should always be possible. This forward declares the typedef identifier and the struct tag at the same time.

Just put such forward declarations of all your struct before the real declarations. As long as you only use pointers to these struct inside the declarations everything should be fine, then.

nitpick: names with a _t are reserved by POSIX. This means that you should avoid it, because some day on some platform there could be a Object_t that is predefined and will conflict with your type.

I personally prefer the following convention

typedef struct Object Object;

so the word Object, with or without struct, always refers to the same.

Upvotes: 5

Related Questions