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)); \
})
编译直接通过,这类错误难以发现。
找了下博客园里有这么篇文章,对于实现细节已经讲的挺详细了。