container_of,顾名思义就是某某某的容器,也就是说某某某成员变量所在的结构体是谁。
struct cntner {
int i; char c; float f;
};
struct cntner init_struct ={.i=2019,'a',0.0120};
char *pc = &init_struct.c;
// 参数1:指向该结构体成员变量的指针;参数2:该结构体变量类型;参数3:该结构体成员变量名
struct cntner *pcntner = container_of(pc, struct cntner, c);
// 然后就可以使用pcntner结构体指针寻找其他成员的值了
printf("init_struct.i = %d\n", pcntner->i);
printf("init_struct.c = %c\n", pcntner->c);
printf("init_struct.f = %f\n", pcntner->f);
有人会问为什么不用init_struct 访问成员变量呢?如果你看了相关系统源码你就知道,很多时候我们访问不了这个结构体变量,只知道其成员,所以用了此招 container_of。
static int fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data)
{
struct fb_event *evdata = data;
int *blank;
struct xxx_ts_data *xxx_data = container_of(self, struct xxx_ts_data, fb_notif);
...
}
一般会使用这个宏接口就行了,若不想放弃请看下面深入探究container_of 宏。
计算出结构体cntner 的地址其实就是通过变量的地址减去自己的偏移量,就这么简单。
cntner_addr = addr_c - offsetof_c
其中变量c的偏移量offsetof_c
= &((struct cntner *)0)->c
,0被强制转化为结构体是不是很高级,其实就是起始地址为0的结构体,然后指向变量c取地址就是c的偏移量。
注:本文代码测试在64位的ubuntu系统中,关于上面字节对齐
疑问请参见:https://blog.csdn.net/u014134180/article/details/78335328#6_488
我们看看下面几个container_of 宏的实现:
这个宏定义出自 msm-4.9\drivers\gpu\drm\nouveau\include\nvif\list.h
/**
* Returns a pointer to the container of this list element.
*
* Example:
* struct foo* f;
* f = container_of(&foo->entry, struct foo, entry);
* assert(f == foo);
*
* @param ptr Pointer to the struct list_head.
* @param type Data type of the list element.
* @param member Member name of the struct list_head field in the list element.
* @return A pointer to the data struct containing the list head.
*/
#ifndef container_of
#define container_of(ptr, type, member) \
(type *)((char *)(ptr) - (char *) &((type *)0)->member)
#endif
下面我们编写一个测试代码
#include
#define container_of(ptr, type, member) ( (type*) ((char*)(ptr) - ((char*)&((type*)0)->member)) )
struct cntner {
int i; char c; float f;
};
int main(void)
{
struct cntner init_struct ={.i=2019,'c',2019.0122};
char *pc = &init_struct.c;
struct cntner *pcntner = container_of(pc, struct cntner, c);
printf(" &init_struct = %p\n", &init_struct);
printf(" = pcntner = %p\n", pcntner);
printf(" (pc) = %p\n", (pc) );
printf("((char*)&((struct cntner*)0)->c) = %p\n", ((char*)&((struct cntner*)0)->c) );
//warning: format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘long int’
//printf(" = %p\n", ((char*)(pc) - ((char*)&((struct cntner*)0)->c)) );
printf("(struct cntner*) ((char*)(pc) - ((char*)&((struct cntner*)0)->c)) = %p\n", \
(struct cntner*)((char*)(pc) - ((char*)&((struct cntner*)0)->c)) );
// output other people
printf("init_struct.i = %d\n", pcntner->i);
printf("init_struct.c = %c\n", pcntner->c);
printf("init_struct.f = %f\n", pcntner->f);
return 0;
}
运行结果如下,读者用其他变量可以测试一下container_of
wucb0122@wucb0122-ubuntu14:~/Codes/test$ gcc container_of.c
wucb0122@wucb0122-ubuntu14:~/Codes/test$ ./a.out
&init_struct = 0x7ffe1a9a7d80
= pcntner = 0x7ffe1a9a7d80
(pc) = 0x7ffe1a9a7d84
((char*)&((struct cntner*)0)->c) = 0x4
(struct cntner*) ((char*)(pc) - ((char*)&((struct cntner*)0)->c)) = 0x7ffe1a9a7d80
init_struct.i = 2019
init_struct.c = c
init_struct.f = 2019.012207
wucb0122@wucb0122-ubuntu14:~/Codes/test$
可以用预处理命令查看宏替换结果(预处理命令
请参看文章 C语言编译流程)
wucb0122@wucb0122-ubuntu14:~/Codes/test$ gcc -E -o container_of.i container_of.c
wucb0122@wucb0122-ubuntu14:~/Codes/test$ cat container_of.i
... ...
int main(void)
{
struct cntner init_struct ={.i=2019,'c',2019.0122};
char *pc = &init_struct.c;
struct cntner *pcntner = ( (struct cntner*) ((char*)(pc) - ((char*)&((struct cntner*)0)->c)) );
... ...
上面用都把地址用(char*)
转换,是以防类型不一致导致编译错误。
这个宏定义出自 msm-4.9\include\linux\kernel.h
,版本二的 container_of(ptr, type, member)
宏的实现包括两部分:
typeof
求出来的类型和ptr不一致,那么编译器会报错,因为ptr和member都是手工输入的参数,宏要保证它们是结构体成员和其相关联指针的一致性。(type *)((char *)mptr - offset)
(注:强转为该结构体指针)/**
- 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) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
替换结果为:
int main(void)
{
struct cntner init_struct ={.i=2019,'c',2019.0122};
char *pc = &init_struct.c;
struct cntner *pcntner = ({ const typeof(((struct cntner *)0)->c) *__mptr=(pc); (struct cntner *) ((char *)__mptr - ((size_t) &((struct cntner *)0)->c)); });
发现在kernel 4.13 的container_of 宏,发现有2个新变化,显得更加高大上了.
https://elixir.bootlin.com/linux/v4.13/source/include/linux/kernel.h#L858
/**
* 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) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
// include/linux/compiler.h
/* Are two types/vars the same type (ignoring qualifiers)? */
#ifndef __same_type
# define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#endi
Wu_Being博客声明:本人博客欢迎转载,请标明博客原文和原链接!谢谢!
《从基本理解到深入探究Linux kernel container_of 宏》:https://blog.csdn.net/u014134180/article/details/86547550