Implementing container_of Using offsetof in C
The container_of macro is a foundational design pattern
in C—most notably used throughout the Linux kernel—that allows a program
to retrieve a pointer to an enclosing structure from a pointer to one of
its internal members. This article explains how the underlying pointer
arithmetic functions, details how the standard offsetof
macro works, and demonstrates how to implement container_of
both in strict standard C and as a type-safe generic macro using
compiler extensions.
The Underlying Mechanics
In C, a structure lays its members out sequentially in contiguous memory, with alignment padding where required. If you know the memory address of a specific member inside an instance of a struct, as well as the exact byte offset of that member from the beginning of the struct, you can find the starting address of the containing structure by subtracting the offset from the member's address.
The standard library header <stddef.h> provides
the offsetof macro:
#include <stddef.h>
offsetof(type, member)This macro returns the offset in bytes of member from
the beginning of type as a size_t.
Standard C Implementation
The simplest, standard-compliant implementation uses pointer
arithmetic by casting the member pointer to a byte pointer
(char *) before performing the subtraction:
#include <stddef.h>
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))(char *)(ptr): Converts the member pointer into a byte-addressed pointer, ensuring the subsequent subtraction decreases the address by single bytes.- offsetof(type, member): Moves the pointer backward by the exact byte distance between the start oftypeand the location ofmember.(type *): Casts the resulting base address to a pointer of the container struct type.
Type-Safe Implementation (GNU C Extensions)
The basic standard implementation lacks compile-time validation: if a
caller accidentally passes an unrelated pointer type as
ptr, the compiler will silently compute an invalid
address.
To prevent this, the Linux kernel uses GNU C extensions—specifically
statement expressions ({}) and the typeof
operator—to enforce type checking:
#include <stddef.h>
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})typeof(((type *)0)->member)extracts the declared type of the specified structure member without evaluating the null pointer dereference at runtime.const typeof(...) *__mptr = (ptr);assigns the supplied pointer to a temporary pointer of the expected type. If the type ofptrdoes not match the member type, the compiler issues a warning.- The last line evaluates to the target container pointer and forms the return value of the statement expression.
Practical Usage Example
The primary use case for container_of is implementing
intrusive data structures, such as linked lists, where the link nodes
are embedded directly inside data objects:
#include <stdio.h>
#include <stddef.h>
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
struct list_node {
struct list_node *next;
};
struct task {
int id;
int priority;
struct list_node node;
};
void process_node(struct list_node *n) {
// Recover the enclosing task struct from the list_node pointer
struct task *t = container_of(n, struct task, node);
printf("Task ID: %d, Priority: %d\n", t->id, t->priority);
}
int main(void) {
struct task my_task = {
.id = 101,
.priority = 5,
.node = { NULL }
};
process_node(&my_task.node);
return 0;
}By decoupling the link element from the data payload,
container_of allows generalized algorithms (such as list
traversal, sorting, and tree balancing) to manipulate node pointers
while allowing user code to seamlessly recover the application-specific
data.