简单又神奇的container_of

Posted 超凡东皇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单又神奇的container_of相关的知识,希望对你有一定的参考价值。

看linux内核代码很多地方都用到了container_of,乍一看它的定义,感觉有点懵,不过实际了解了却发现实现的非常简单,由这个宏引发了我几点感慨:

①一行代码的威力竟有如此之大

②开发内核的大佬,恐怖如斯

③没事要多看看内核代码

为何会有这些感慨,且听我细细道来...

一行代码的威力

说到一行代码的威力,且看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) );)

该宏的作用就是通过结构体中一个成员的地址(ptr),以及该成员的类型(type)与名字(member)获得该结构体的地址,从而访问该结构体中其它成员,对,就这么简单,这个类似面向对象中通过父类指针访问子类对象成员,也就是说通过container_of从c语言的面向过程编程直接过渡到了面向对象编程,也就是因为这个宏,linux内核发生了天翻地覆的变化...

container_of(ptr, type, member)的简化

看了container_of的定义,第一行就是一个指针赋值,功能上面没有起什么作用,我们可以简化一下变为:

#define container_of(ptr, type, member) (                      \\
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \\
        (type *)( (char *)__mptr - offsetof(type,member) );)
                            |
                            |
                            V
#define container_of(ptr, type, member) (                      \\
        (type *)( (char *)ptr - offsetof(type,member) );)

简化之后的功能完全是一样的,等会我们再讲去掉的那行的含义,了解container_of这个宏就是了解 (type *)( (char *)ptr - offsetof(type,member) )这行代码了,而了解这行代码我们需要先来了解一下offsetof这个宏

offsetof宏

前面的(char *)ptr我们知道是将ptr指针强制转换为char*型指针,就是转换为字节型的好计算,后面的offsetof这个宏定义如下:

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

这个宏的意思就是将0地址强制转换为(TYPE *)结构体指针类型,然后->MEMBER是指向MEMBER成员,然后&是将该成员的地址取出来,实际就是相对结构体0地址的偏移位置,最后转换为size_t类型,size_t一般为长整型,整个过程如下:

					 0			    //0地址
		    ((TYPE *)0)			    //强制转换为(TYPE *)结构体指针
		   (((TYPE *)0)->MEMBER)	//指向结构体成员
		  &(((TYPE *)0)->MEMBER)	//取成员地址,就是相对0地址的偏移位置
((size_t) &(((TYPE *)0)->MEMBER)    //转换为size_t类型

总之,这个宏就是返回一个成员相对基地址的偏移位置。

然后整体看(type *)( (char *)ptr - offsetof(type,member) ),就是将结构体成员地址强制转换为字符型指针减去其相对结构体首地址的偏移量,最后在转换为(type *)类型指针,实际得到的就是结构体首地址.

const typeof( ((type *)0)->member ) *__mptr = (ptr); 

最后我们来看一下去掉的那行代码的意义,const typeof( ((type *)0)->member ) *__mptr = (ptr); 

typeof在标准C中是不支持的,在gnu扩展语法中才可以支持;它的作用就是:比如定义 int a;

通过typeof(a)就可以返回int这个类型,typeof( ((type *)0)->member )这里就是获得成员member的类型,const typeof( ((type *)0)->member ) *__mptr = (ptr);就是定义了一个新的跟成员member类型一致的常指针__mptr,并且赋值指向ptr,这么做有何意义呢?直接使用ptr不就好了吗,多此一举?这么做是很有意义的,是为了防止开发人员使用container_of时,如果误传指针就会报错,从而减小开发出错的可能,这里不得不佩服开发内核的大佬实在是厉害,考虑问题的全面与细致,真是恐怖如斯~

移植与测试

这么好的宏怎么可能不把它移植到mcu上去呢,因为mcu编译器并不支持typeof,所以我就把中间那行直接去掉了,使用的时候就需注意别传错指针了,因为传错了也不会报错,注意就不会影响使用,代码如下:


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

#define container_of(ptr, type, member) ( \\
	(type *)( (char *)(ptr) - offsetof(type,member) ))

struct test 
	int 	i;
	char 	c;
	float 	f;
;

void test_container(void)

	struct test temp= 8, 'b', 22.6;
	struct test *tp = container_of(&temp.c, struct test, c);
	
	printf("&temp 	= %p\\n",&temp);
	printf("&tp 	= %p\\n\\n",tp);
	
	printf("tp.i 	= %d\\n",tp->i);
	printf("tp.c 	= %c\\n",tp->c);
	printf("tp.f 	= %0.1f\\n\\n",tp->f);	
	
	printf("sizeof-c:%d\\n", offsetof(struct test, c));

运行结果:

 移植成功,能得到预期的结果,,,

以上是关于简单又神奇的container_of的主要内容,如果未能解决你的问题,请参考以下文章

简单又神奇的container_of

神奇的container_of()宏

神奇又简单的抽卡游戏c++

container_of

设备接口体应用container_of的思考

内核中container_of宏的详细分析