Of course! In C, the container_of macro is a fundamental and powerful tool, especially in the Linux kernel and other projects that implement custom data structures. It's the key to moving from a pointer to a structure's member back to a pointer to the entire structure.

Let's break it down in detail.
What is container_of?
In simple terms, container_of is a macro that calculates the address of a parent structure given the address of one of its members.
Analogy: Imagine you have a car (the "container" or "parent structure"). The car has an engine (a "member"). If you are given a pointer to the engine, how do you find the address of the entire car?
- You need to know the size of the car.
- You need to know the offset of the engine from the start of the car.
You can then calculate: car_address = engine_address - engine_offset.

container_of does exactly this calculation at compile time.
The Problem It Solves
In C, you often work with pointers to structures. However, sometimes you only have a pointer to a field inside a structure. For example, the Linux kernel's linked list implementation is a classic case.
Imagine this structure:
struct task_struct {
int pid;
char name[16];
struct list_head tasks; // This is the node for the linked list
// ... many other fields
};
The struct list_head is a standard, generic linked list node. The kernel's list functions (like list_for_each_entry) operate on struct list_head*. When you iterate through a list of task_struct objects, you get a pointer to the tasks member of each object. But you don't want the tasks member; you want the whole task_struct! How do you get from &some_task->tasks back to some_task?

This is the exact problem container_of solves.
The Implementation and How It Works
Here is the standard definition of container_of, commonly found in the Linux kernel:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type, member) ); \
})
This looks complex, but let's break it down piece by piece.
A. The Parameters:
ptr: A pointer to thememberinside the structure.type: The data type of the parent structure (e.g.,struct task_struct).member: The name of the member within the structure (e.g.,tasks).
B. The Components Inside:
-
offsetof(type, member):- This is a standard C macro (from
<stddef.h>) that calculates the byte offset ofmemberfrom the beginning oftype. - For example,
offsetof(struct task_struct, tasks)would return the number of bytes from the start ofstruct task_structto the start of itstasksfield.
- This is a standard C macro (from
-
*`typeof( ((type )0)->member )`**:
- This is a GCC/Clang extension (not standard C) that gets the data type of the
member. ((type *)0)->memberis a clever trick: it accesses a member of a structure that is at address 0. This is safe because we are not dereferencing the pointer, just using it to determine the type of the member.typeof(...)then captures this type. For our example, it would be the type ofstruct list_head.
- This is a GCC/Clang extension (not standard C) that gets the data type of the
-
*`const typeof(...) __mptr = (ptr);`**:
- This declares a temporary pointer
__mptrof the correct type (const struct list_head*in our example). - It's initialized with the user-provided
ptr. This step is crucial for type safety. It ensures that if you pass avoid*or an incorrect pointer type, the compiler will warn you.
- This declares a temporary pointer
-
*`(char )__mptr - offsetof(...)`**:
- We cast the member pointer
__mptrto achar*.char*arithmetic is special: subtracting an integer from it moves the pointer backward by that many bytes. - We subtract the offset of the member. This moves the pointer from the member's location back to the beginning of the parent structure.
- We cast the member pointer
-
*`(type )`**:
- Finally, we cast the resulting
char*(which points to the start of the structure) back to the parent structure's type (struct task_struct*).
- Finally, we cast the resulting
-
:
This is a GCC extension called a "statement expression". It allows a block of statements to be used as an expression. The value of the last statement in the block is returned. This is a clean way to write a multi-line macro that returns a value.
A Complete, Runnable Example
Let's create a simple program that demonstrates container_of in action. We'll first define our own version of offsetof and container_of to avoid relying on GCC extensions for the sake of portability in this example.
#include <stdio.h>
#include <stdlib.h>
// Our own offsetof implementation (standard C)
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
// Our own container_of implementation without typeof for portability
// Note: A real implementation would use typeof for type safety.
#define container_of(ptr, type, member) \
((type*) ((char*)(ptr) - offsetof(type, member)))
// A simple structure
struct my_struct {
int id;
float value;
char name[10];
};
int main() {
// 1. Create an instance of our structure
struct my_struct obj = {42, 3.14f, "Test"};
// 2. Get a pointer to one of its members (e.g., 'value')
float *value_ptr = &obj.value;
// 3. Use container_of to get back a pointer to the whole object
struct my_struct *obj_ptr = container_of(value_ptr, struct my_struct, value);
// 4. Verify that the pointers are the same
if (obj_ptr == &obj) {
printf("Success!\n");
printf("Original object: id=%d, value=%f, name=%s\n", obj.id, obj.value, obj.name);
printf("Retrieved object: id=%d, value=%f, name=%s\n", obj_ptr->id, obj_ptr->value, obj_ptr->name);
} else {
printf("Failure!\n");
}
// Let's prove the offset calculation is correct
printf("\n--- Verification ---\n");
printf("Address of 'obj': %p\n", (void*)&obj);
printf("Address of 'obj.value': %p\n", (void*)&obj.value);
printf("Calculated offset of 'value': %zu\n", offsetof(struct my_struct, value));
printf("Difference in addresses: %zu\n", (size_t)((void*)&obj.value - (void*)&obj));
return 0;
}
Output of the program:
Success!
Original object: id=42, value=3.140000, name=Test
Retrieved object: id=42, value=3.140000, name=Test
--- Verification ---
Address of 'obj': 0x7ffc... (some address)
Address of 'obj.value': 0x7ffc... (address + 4, since int is usually 4 bytes)
Calculated offset of 'value': 4
Difference in addresses: 4
Common Use Case: Linked Lists
This is where container_of shines. The kernel's list macros are a perfect example.
#include <stdio.h>
#include <stdlib.h>
// Simplified kernel list implementation
struct list_head {
struct list_head *next, *prev;
};
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
#define container_of(ptr, type, member) \
((type*) ((char*)(ptr) - offsetof(type, member)))
struct task {
int pid;
char name[16];
struct list_head list; // The list node
};
// A dummy function to initialize a list
void init_list_head(struct list_head *head) {
head->next = head;
head->prev = head;
}
// A dummy function to add an item to a list
void list_add(struct list_head *new, struct list_head *head) {
new->next = head->next;
new->prev = head;
head->next->prev = new;
head->next = new;
}
int main() {
struct task task1 = {1, "ls"};
struct task task2 = {2, "cat"};
struct list_head head;
init_list_head(&head);
// Add tasks to the list
list_add(&task1.list, &head);
list_add(&task2.list, &head);
// Iterate through the list
printf("Iterating through tasks:\n");
struct list_head *pos;
for (pos = head.next; pos != &head; pos = pos->next) {
// 'pos' points to the 'list' member of a 'struct task'
// Use container_of to get the full 'struct task'
struct task *t = container_of(pos, struct task, list);
printf("PID: %d, Name: %s\n", t->pid, t->name);
}
return 0;
}
Output:
Iterating through tasks:
PID: 2, Name: cat
PID: 1, Name: ls
(Note: The order is reversed because we are adding to the head of the list).
Summary: Pros and Cons
Pros:
- Powerful Generic Programming: Allows for the creation of generic data structures (like lists, trees) that can hold any type of data.
- Performance: It's a simple macro that performs arithmetic at compile time. There is zero runtime overhead.
- Core of Kernel Design: It's essential for complex kernel subsystems (like process management, device drivers) to manage collections of objects efficiently.
Cons:
- Readability: The macro definition is cryptic and can be intimidating for beginners.
- Portability: The standard
container_ofrelies on GCC extensions (typeof, statement expressions). While most modern compilers support them, strictly speaking, it's not standard C. However, a portable version can be written (as shown above) at the cost of some type safety. - Safety: It's a "trust the programmer" tool. If you pass the wrong
typeormembername, you will get a wrong pointer, leading to memory corruption and crashes, which the compiler might not catch.
