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:
- Extract a pointer to a concrete type from the interface type using
container_of
. - 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;