A Solution For Polymorphism in C

When crafting software in C language for embedded systems, sometimes you need more than the language can offer. Its closeness to the hardware means that, sometimes, it’s a bit far from what a high level language can offer.

The implementation of polimorphism is, from my point of view, one of those things that requires emulation through tricky constructs, always with drawbacks.

After much time looking for a solution to this problem, I came up with one that, I think, is the closest you can get to object oriented programming. This solution features:

  • One depth class hierarchy.
  • Static declaration of any object from the class hierarchy.

A model for C

First of all, let’s define the Interface. For the interface, I’m going to use a very common technique, creating a struct with a set of function pointers that define the available methods. Notice the incomplete type declaration for struct parent_struct, required by the lack of support in C for forward declarations. Also, notice that we have to pass explicitly the reference to the object as the first argument of each method. The destructor is included as part of the interface.

// parent.h

struct parent_struct;

struct Interface {
    int (*foo)(struct parent_struct *, int);
    void (*bar)(struct parent_struct *);
    void (*destroy)(struct parent_struct *);
};

The next step is to define the parent class. The parent class contains a reference to the Interface, plus the variables common to the class hierarchy. As a personal convention, I typedef the structs I’m going to use as class counterparts in C.

// parent.h

struct parent_struct;

struct Interface {
    int (*foo)(struct parent_struct *, int);
    void (*bar)(struct parent_struct *);
    void (*destroy)(struct parent_struct *);
};

typedef struct parent_struct {
    struct Interface *ops;
    int common_var;
} Parent;

In order to simplify usage, I then create a number of wrapper functions to avoid having to reference the internal ops variable. This functions are included in the header file and are specified as static inline.

// parent.h

struct parent_struct;

struct Interface {
    int (*foo)(struct parent_struct *, int);
    void (*bar)(struct parent_struct *);
    void (*destroy)(struct parent_struct *);
};

typedef struct parent_struct {
    struct Interface *ops;
    int common_var;
} Parent;

static inline int ParentFoo(Parent *parent, int a) {
    return parent->ops->foo(parent, a);
}

static inline void ParentBar(Parent *parent) {
    parent->ops->bar(parent);
}

static inline void ParentDestroy(Parent *parent) {
    parent->ops->destroy(parent);
}

Now comes the tricky part, the implementation of the child class. We take advantage of the fact that the address of the first element of a struct is the same as the address of the struct to emulate LSP. That’s why we require having a variable of the Parent type as the first variable of the child class. In C11, this requirement can be enforced with the use of the static assert declaration. In previous versions of C, other techniques can be used for static assertions (however, they are only used by people that live in caves and use their nose as the preferred method of pressing the keys).

// child.h

#include <parent.h>

typedef struct child_struct {
    Parent parent;
    int child_var;
} Child;

Next part, I’m going to define the child class methods prototypes. The child class methods are both publicly available (in case only the child class is used) and available through the parent’s interface. A ChildInit(Child *child) method is included in the child class as an emulator of the constructor.

// child.h

#include <parent.h>

typedef struct child_struct {
    Parent parent;
    int child_var;
} Child;

int ChildInit(Child *child);

int ChildFoo(Parent *parent, int a);

void ChildBar(Parent *parent);

void ChildDestroy(Parent *parent);

Notice two things in the above code:

  1. The ChildFoo, ChildBar and ChildDestroy methods are the implementation of the interface and they require to be compatible with it. As a result they receive a Parent * type as their first argument.
  2. The constructor receives a Child * (obviously, as we are initializing an object of type Child *). I prefer this implementation of a constructor (as oposed to allocating the required memory in the constructor), as it leaves the memory allocation stuff to the client of the interface.

Finally, let’s implement the child class.

// child.c

#include <child.h>

static struct Interface child_ops = {
    .foo = ChildFoo,
    .bar = ChildBar,
    .destroy = ChildDestroy,
}

int ChildFoo(Parent *parent, int a) {
    Child *child = (Child *)parent;
    /* Do foo */
}

void ChildBar(Parent *parent) {
    Child *child = (Child *)parent;
    /* Do bar */
}

int ChildInit(Child *child) {
    child->parent.ops = &child_ops;
    /* Other initialization stuff */
}

void ChildDestroy(Parent *parent) {
    Child *child = (Child *)parent;
    /* Do destroy */
}

A sample Implementation

Now, how would you use this stuff?. I’ve uploaded a sample implementation of a logging system to the Software Daily Crafts GitHub that uses the techniques described here.

Final thoughts

Software design in C can be a challenging problem. The best design techniques and top books are usually object-centric, which leaves a gap in the embedded systems world (synonym of C). Where there is a lack in language constructs, non-standard conventions can fill the gap. Some of these conventions can allow us to embrace good design practices.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s