containerof如何从成员地址获取结构体首地址?

99ANYc3cd6
预计阅读时长 35 分钟
位置: 首页 C语言 正文

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.

c语言 container of
(图片来源网络,侵删)

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.

c语言 container of
(图片来源网络,侵删)

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?

c语言 container of
(图片来源网络,侵删)

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 the member inside 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:

  1. offsetof(type, member):

    • This is a standard C macro (from <stddef.h>) that calculates the byte offset of member from the beginning of type.
    • For example, offsetof(struct task_struct, tasks) would return the number of bytes from the start of struct task_struct to the start of its tasks field.
  2. *`typeof( ((type )0)->member )`**:

    • This is a GCC/Clang extension (not standard C) that gets the data type of the member.
    • ((type *)0)->member is 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 of struct list_head.
  3. *`const typeof(...) __mptr = (ptr);`**:

    • This declares a temporary pointer __mptr of 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 a void* or an incorrect pointer type, the compiler will warn you.
  4. *`(char )__mptr - offsetof(...)`**:

    • We cast the member pointer __mptr to a char*. 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.
  5. *`(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*).
  6. :

    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_of relies 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 type or member name, you will get a wrong pointer, leading to memory corruption and crashes, which the compiler might not catch.
-- 展开阅读全文 --
头像
dede织梦如何彻底去除powered版权?
« 上一篇 2025-12-16
dede css/js文件目录如何安全优化管理?
下一篇 » 2025-12-16

相关文章

取消
微信二维码
支付宝二维码

目录[+]