Posts

A C Of Polymorphism Without CPP

Polymorphism in C using real codebases

Introduction

Polymorphism sounds like a C++ word. In C, it still exists, just without the sugar. You create a base type, keep a table of function pointers, and pass that base pointer around.

A Small Example

Here’s an example. This is the base class.

typedef struct Animal Animal;

struct Animal {
    char* name;
    void (*speak)(Animal* self);
};

Now the child types.

typedef struct {
    Animal base;
} Dog;

typedef struct {
    Animal base;
} Cat;

The polymorphic functions live in the child types. There is no default animal_speak(), so this behaves like a pure virtual method.

static void dog_speak(Animal* self) {
    printf("%s: woof\n", self->name);
}

static void cat_speak(Animal* self) {
    printf("%s: meow\n", self->name);
}

We also have a common destructor in the parent that both child types “inherit”.

static void animal_free(Animal* self) {
    if (!self) {
        return;
    }
    free(self->name);
    memset(self, 0, sizeof(*self));
}

Creation ties everything together.

static Dog dog_new(const char* name) {
    Dog dog = {0};
    dog.base.name = strdup(name);
    dog.base.speak = dog_speak;
    return dog;
}

static Cat cat_new(const char* name) {
    Cat cat = {0};
    cat.base.name = strdup(name);
    cat.base.speak = cat_speak;
    return cat;
}

Finally, this is how the whole setup is used.

int main(void) {
    Dog rex = dog_new("rex");
    Cat luna = cat_new("luna");

    Animal* pets[] = { (Animal*)&rex, (Animal*)&luna };
    for (size_t i = 0; i < 2; i++) {
        pets[i]->speak(pets[i]);
    }

    animal_free((Animal*)&rex);
    animal_free((Animal*)&luna);
}

The base struct stores the function pointer. The derived struct just fills it in. In main, both Dog and Cat go through the same speak() call. That’s the “overloading” point in C: one call site, multiple implementations.

However!

Multiple inheritance is tricky in C. You can embed multiple base structs, but then casting gets dangerous and you need to manage offsets yourself. That’s why GLib/GTK keep a single parent and use interfaces when they need to mix behavior.

Real World Use

GTK: GtkButton is a GtkWidget. The instance struct embeds GtkWidget and the class struct GtkButtonClass stores function pointers like clicked and activate. That’s polymorphism as a widget hierarchy.

GLib: GMemoryMonitor is an interface (vtable), GMemoryMonitorBase is the abstract parent, and GMemoryMonitorPoll / GMemoryMonitorPsi are concrete children. The default implementation is picked at runtime via g_memory_monitor_dup_default().

Linux kernel: struct file_operations is the vtable. Each filesystem or driver fills in the function pointers it supports, and the VFS calls through those pointers.