offsetof 和 container_of 的用途

offsetof() 和 container_of() 两个宏定义在Linux内核中很常见。我们在应用程序编程时可能也需要用到,因此可以把这两个宏定义copy过来。

offsetof(type, member)用来求结构体type中的member成员在type中的偏移, 其定义如下:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

例如对于结构体:

struct test_s {
    int a;
    int b;
    int c;
};

那么 offsetof(struct test_s, c) 就等于8。

container_of(ptr, type, member)宏用来通过结构体的某个成员的指针来获得指向整个结构体的指针。

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:   the type of the container struct this is embedded in.
 * @member: the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({          \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

其中,type是结构体的类型,member是结构体中这个成员的名字,ptr是指向这个成员的实例的指针。例如对于结构体:

struct camdev {
    int a;
    int c;
    int b;
};

那么下面代码的结果就是将指针zzp指向了结构体zz。

struct camdev zz; //一个结构体实例
struct camdev * zzp;
zzp = container_of(&(zz.c), struct camdev, c);

当然这个代码只是举例用的,实际场景可能是,我们不知道zz在哪定义及其地址,但知道c是这个结构体的成员且知道c的指针,这时就可以用container_of获取zz的实例。

-
需要说明两点:
1. offsetof()宏展开后的内容是在编译阶段(编译过程的第二阶段)被处理掉的,而不是运行时,所以用0地址来取成员不会出现异常。
2. container_of()和offsetof()是内核中提供的宏,我们写应用程序时不能直接用,而是要重新定义。

随便再说一点:可计算的成员偏移都可以在编译阶段得出结果,例如我看到一个程序如下:

#include <stdio.h>

struct str{
    int len;
    char s[0];
};

struct foo {
    struct str *a;
};

int main(int argc, char** argv) {
    struct foo f={0};
    if (f.a->s) {
        printf("%x\n", f.a->s);
    }
    return 0;
}

这个程序执行时并不会报错,打印结果为4。因为虽然 f = {0} 是将a指向了NULL,但代码中printf是在打印 f.a->s 的地址,这在编译期间就可以算出来。如果把%x改为%s程序就崩溃了,因为你试图访问s指向的内容。

猜你喜欢

转载自blog.csdn.net/jasonchen_gbd/article/details/80069162