Interfaces in C using container_of

I used to think that abstract types was something one would just not do in C. However, I recently (re)discovered the container_of macro1 and have changed my mind.

The pattern is the following: your interface is a struct containing function pointers – "methods" – acting on itself. It looks like this:

#include <stdio.h>
typedef struct  {
    float (*area)(struct shape_t * sh);
    // ... There could be other function pointers.
} shape_t;

If you wanted to implement the interface with a concrete type, you would embed the interface struct into your concrete struct like this:

#include <stdio.h>
// for typeof
#include <stddef.h>

typedef struct shape {
    float (*area)(struct shape_t * sh);
    // ... There could be other function pointers.
} shape_t;

typedef struct circle {
    float radius;
    shape_t shape_iface;
} child_t;

typedef struct square {
    float side_length;
    shape_t shape_iface;
} child_t;

Interfaces usually carry some kind of state, so the concrete implementation of the area field would need to compute the pointer to the concrete type circle or square from a pointer to the interface struct. This is where the container_of macro comes in.

// The container_of macro
#define container_of(expr, type, field) ({ \
            /* This first line is only useful to do type_checking */     \
            typeof(((type *) 0) -> field) * __type_checking_ptr = (expr); \
            (type*)(((void*) __type_checking_ptr) - offsetof(type, field)); \
        })

This macro does funky pointer arithmetic to calculate the pointer of a whole struct just from the pointer to one of its field.

When using this interface pattern, you would generally initialise the function table at the same time as the rest of your struct and you would pass the interface around as a pointer. Methods would follow the following pattern:

  1. Extract a pointer to a concrete type from the interface type using container_of.
  2. Act on the concrete struct through the pointer.

Here is a working example of this pattern:

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

// The container_of macro
#define container_of(expr, type, field) ({ \
            /* This first line is only useful to do type_checking */     \
            typeof(((type *) 0) -> field) * __type_checking_ptr = (expr); \
            (type*)(((void*) __type_checking_ptr) - offsetof(type, field)); \
        })

typedef struct shape {
    float (*area)(struct shape * sh);
    // ... There could be other function pointers.
} shape_t;

typedef struct square {
    float side_length;
    shape_t shape_iface;
} square_t;

typedef struct circle {
    float radius;
    shape_t shape_iface;
} circle_t;

// First concrete implementation
float square_get_area(shape_t * iface) {
    // We use the container_of macro to get a function pointer to the
    // object the person_t struct is embedded in.
    square_t * square = container_of(iface, square_t, shape_iface);
    return square->side_length*square->side_length;
}

void square_init(square_t * out, float side_length) {
    out->side_length = side_length;

    shape_t iface = {
        .area = square_get_area,
    };

    out->shape_iface = iface;
}

// Second concrete implementation
float circle_get_area(shape_t * iface) {
    circle_t * circle = container_of(iface, circle_t, shape_iface);
    float r = circle->radius;
    return 3.1415 * r*r;
}

void circle_init(circle_t * out, float radius) {
    out->radius = radius;

    shape_t iface = {
        .area = circle_get_area,
    };

    out->shape_iface = iface;
}

// An example of a function using the interface.
void print_area(shape_t * shape) {
    printf("the area is  %1.1f\n",shape->area(shape));
}

int main() {
    circle_t circle;
    square_t square;

    square_init(&square, 1.0);
    circle_init(&circle, 1.0);

    print_area(&circle.shape_iface);
    print_area(&square.shape_iface);

    return 0;
}

This program outputs:

1. Subtyping

using this pattern, it is also possible to represent subtyping by adding an interface type as a field to another interface type.

#include <stdio.h>
typedef struct triangle {
    float (*angle1)(struct triangle *);
    float (*angle2)(struct triangle *);
    float (*angle3)(struct triangle *);

    shape_t shape_iface;
    // ... There could be other function pointers.
} triangle_t;

Footnotes:

1

This macro is not part of any standard, but it is fairly easy to implement. Some other languages, like Zig have something similar in their standard library.

Author: Justin Veilleux

Created: 2025-09-25 Thu 18:11

Validate