Linux下的Container_of

Linux下的Container_of

看内核代码的时候看到了container_of宏,写的真是挺巧妙的,直接起到了this指针的功能

define container_of(ptr, TYPE, MEMBER)({ \

const typeof(((TYPE )0)->MEMBER) _mptr = (ptr);
(TYPE
)((char
)_mptr - offsetof(TYPE, MEMBER));
})

当时主要有两个疑问:

  • 为什么要有个临时变量_mptr?直接使用ptr不就可以了?
  • 为什么要给mptr强转成char*?

写了段代码
Container_of的测试代码:

include <stdio.h>

define offsetof(TYPE, MEMBER)({(size_t) &(((TYPE *)0)->MEMBER);})

define container_of(ptr, TYPE, MEMBER)({ \

const typeof(((TYPE )0)->MEMBER) _mptr = (ptr);
(TYPE
)((char
)_mptr - offsetof(TYPE, MEMBER));
})

struct stA{
char a;
int b[3];
double c;
};

int main (void)
{
struct stA A;
double* x = &(A.c);

printf("%p\n",&A);
printf("%p <-- %p\n", x, container_of(x, struct stA, c));
return 0;
}

这份代码输出如下:
0x7fff5718ebd0
0x7fff5718ebe0 <-- 0x7fff5718ebd0

如果去掉_mptr前的(char),输出则为:
0x7fff519e6bd0
0x7fff519e6be0 <-- 0x7fff519e6b60
因为C语言指针变量的差值是根据指针变量类型来计算的,_mptr是double类型(8字节),实际上的结果是8
offsetof(TYPE, MEMBER)),这样计算地址当然会出错。

而对于

const typeof(((TYPE *)0)->MEMBER) *_mptr = (ptr);

一句的解释,参考了这篇文章的描述:
这句话主要作用是在编译时,能够让编译器检查ptr的类型和MEMBER的类型是否匹配,如果不匹配则产生相应的告警。
例如,

include <stdio.h>

define offsetof(TYPE, MEMBER)({(size_t) &(((TYPE *)0)->MEMBER);})

define container_of(ptr, TYPE, MEMBER)({ \

const typeof(((TYPE )0)->MEMBER) _mptr = (ptr);
(TYPE
)((char
)_mptr - offsetof(TYPE, MEMBER));
})

struct stA{
char a;
int b[3];
double c;
};

int main (void)
{
struct stA A;
double* x = &(A.c);

printf("%p\n",&A);
printf("%p <-- %p\n", x, container_of(x, struct stA, a)); //这里把成员变量名错写成a
return 0;
}
编译器在编译时会告警:

warning: incompatible pointer types initializing 'const typeof (((struct stA *)0)->a) *' (aka 'const char *') with an expression of type 'double *' [-Wincompatible-pointer-types]

如果改成这样:

define container_of(ptr, TYPE, MEMBER)({ \

(TYPE)((char)ptr - offsetof(TYPE, MEMBER));
})

编译直接通过,这类错误难以发现。

找了下博客园里有这么篇文章,对于实现细节已经讲的挺详细了。

posted @ 2015-02-28 00:38  NumberSix  阅读(783)  评论(0)    收藏  举报