Skip to content

C Tip: Forward Declaring Structs

Forward declarations in C allow you to declare an object type before actually defining it (you can also do this in C++, but the process is not exactly the same - this tip focuses on C). The most common use of this technique in C is to declare pointers to a type to be used in the definition of the same type:


typedef struct listNode_s *listNode_tptr;
typedef struct listNode_s
{
   void *item;
   listNode_tptr next;
} listNode_t;

If you’ve never seen this before or don’t understand what’s going on, that’s okay. An article on struct semantics in C is forthcoming. Suffice it to say, the code works.

Code like that above is very common. It declares a forward reference of a struct pointer. Less common, and perhaps less well known, is that you can also forward declare a struct in C (not just struct pointers). The syntax is the same:


typedef struct listNode_s listNode_t;
typedef struct listNode_s *listNode_tptr;
typedef struct listNode_s
{
   void *item;
   struct listNode_tptr next;
};

At first glance, this doesn’t appear very useful. But it is a great mechanism for implementation hiding. Implementation hiding in C is often implemented through a handle system, or a very basic system of using void pointers (typedef void *MyType). Both mechanisms require a great amount of casting or some form of handle to object conversion internally. By forward declaring the struct type, you make life much easier for yourself:


// myType.h
typedef struct myType_s myType_t;
myType_t *createMyType(int val);

// myType.c
typedef struct myType_s
{
   int myVal;
};
myType_t *createMyType(int val)
{
    myType_t *mt = (myType_t *)malloc(sizeof(myType));
    mt->myVal = val;
    return mt;
}

This approach shouldn’t be used if others will use your code and you don’t want instances of myType_t to be manipulated outside of myType.c. Anyone using your module (or library as the case may be) can manipulate the fields of your forwared declared struct instances if they know the layout in memory. In those cases, you should use a handle based system.

I use this technique to avoid pulling in includes that users of myType don’t need. For example, a logger object that has a FILE* field type will need to include stdio.h. If the logger struct is defined in a logger header, the header will need to include stdio.h and all modules that include the logger header will also pull in stdio.h. On a large project, this sort of thing can affect compile times. Forward declaring the logger struct and actually defining it in the source module keeps stdio.h included only where it is needed.

An article explaining the mechanics behind this is forthcoming.

Post a Comment

Your email is never published nor shared. Required fields are marked *