offsetof 和 container_of 宏是 C 语言中常用于结构体操作的两个宏,它们帮助程序员处理指针偏移和结构体的成员与结构体本身之间的关系。下面是对这两个宏的总结及其应用的详细介绍。

1. offsetof 宏

作用

offsetof 宏用来获取结构体成员相对于结构体起始位置的偏移量。它返回一个 size_t 类型的值,表示成员在结构体中的偏移。

定义

在 <stddef.h> 头文件中定义:

#define offsetof(type, member)  ((size_t)&(((type *)0)->member))

用法

  • type: 结构体类型。
  • member: 结构体成员。

offsetof(type, member) 的作用是返回 member 成员相对于 type 类型结构体首地址的偏移值。

示例

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

struct Person {
    int age;
    char name[20];
};

int main() {
    printf("Offset of age: %zu\n", offsetof(struct Person, age));
    printf("Offset of name: %zu\n", offsetof(struct Person, name));
    return 0;
}

输出:

Offset of age: 0
Offset of name: 4

解释:

  • age 成员位于结构体的起始位置,因此偏移量为 0。
  • name 成员紧随其后,假设 int 占 4 字节,那么 name 的偏移量是 4。

2. container_of 宏

作用

container_of 宏用来根据结构体成员的地址获取指向该结构体的指针。它实现了从结构体的成员指针逆推回结构体指针的功能。

定义

#define container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - offsetof(type, member)))
  • ptr: 指向结构体成员的指针。
  • type: 结构体类型。
  • member: 结构体的成员。

原理

  • ptr 是指向成员的指针。
  • offsetof(type, member) 计算出成员相对于结构体起始位置的偏移量。
  • 通过 ptr 减去偏移量就能得到结构体的起始地址,即结构体指针。

示例

假设你有以下结构体:

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

struct Person {
    int age;
    char name[20];
};

int main() {
    struct Person person = {25, "John"};
    int *ptr = &person.age;

    struct Person *person_ptr = container_of(ptr, struct Person, age);

    printf("Person's age: %d\n", person_ptr->age);
    printf("Person's name: %s\n", person_ptr->name);

    return 0;
}

输出:

Person's age: 25
Person's name: John

解释:

  • ptr 是指向 person.age 的指针。
  • container_of(ptr, struct Person, age) 通过 age 成员的偏移量计算出 person 结构体的起始地址,即获得 person_ptr 指针。
  • 然后可以使用 person_ptr 访问结构体的其他成员。

offsetof 和 container_of 的关系

  • offsetof 主要用于获取结构体成员相对于结构体起始位置的偏移量,帮助我们计算结构体成员的内存位置。
  • container_of 则反过来,利用成员的指针和偏移量推算出包含该成员的结构体指针。它用于从成员指针恢复出结构体指针,特别在链表操作或遍历中非常常见。

实际应用场景

  1. 链表操作
    在内核编程(如 Linux 内核)中,链表节点结构常常嵌入在结构体中,使用 container_of 宏可以方便地从链表节点指针获取完整的结构体指针。struct list_head { struct list_head *next, *prev; }; struct Person { int age; char name[20]; struct list_head list; }; void print_person_info(struct list_head *node) { struct Person *person = container_of(node, struct Person, list); printf("Age: %d, Name: %s\n", person->age, person->name); }
  2. 事件处理和回调
    在事件驱动的编程中,回调函数常常会传递结构体成员的指针,通过 container_of 可以还原出回调函数所属的整个结构体指针。
  3. 数据结构优化
    在自定义的数据结构中,可能会嵌入结构体的部分成员,并使用 container_of 来高效地从数据结构的部分恢复完整的结构体指针。

总结

  • offsetof:用于计算结构体成员相对于结构体首地址的偏移量,通常用于需要处理内存布局和数据结构的场景。
  • container_of:用于通过结构体成员的指针逆推出包含该成员的结构体指针,广泛应用于内核编程、数据结构(如链表)操作中。

这两个宏可以帮助开发者在内存管理和数据结构操作时更加高效地进行指针运算,减少内存访问错误,并提升代码的可读性和可维护性。