我也来说说container_of(或者list_entry)

还是从头说起吧,也给自己记一下。

你是怎么定义链表的?如果昨天你问我这个问题,我大概会写出以下的代码:

struct dog{
unsigned char name[10];
struct dog*prev;
struct dog *next;
}

但是内核可不能这么做,(以前确实是这么做的,从2.1开始就使用了官方的实现方法)因为这样的链表只适用于这一个结构体,你以后要想实现一个struct cat就得写另一个链表。内核里需要链表的结构体多如牛毛,要全都写上那该多费事啊……聪明的开发团队想到了下面的实现方法:

struct list_head{
struct list_head *next;
struct list_head *prev;
};
struct dog{
unsigned char name[10];
struct list_head list;
}

也就是说,以后想要实现链表只需在用户结构体上增加一项内核定义的结构体struct list_head。从名字就可以看出,next指向了下一个元素,prev指向了上一个。

对,这样是可以把相应的list_head连接起来,所有的程序都可以使用同样的方法,确实简便了,可是似乎有点问题?我无法获得list_head所在的父结构体啊!我只能操作list_head有什么用!

所以,为了获取list_head所在父结构体,接下来就需要主角出场了:container_of(或者list_entry)。实际上:

#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

我们在list.h文件中找到container_of的宏定义:

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

这个宏接受三个参数,一般说来,ptr的类型是struct list_head*,它就是你提供的list_head,它存在于你想要找到的父结构体中,是三个参数里唯一一个在运行时才有确切值的;而type是父结构体的类型;member是struct list_head在父结构体中的名字。

使用方法如下:

struct dog mdog;
struct dog * ptr_dog=container_of(&mdog.list,struct dog,list);

接下来好好看看它是怎么实现的吧。

1.typeof

typeof(int) a;将a定义为int型

typeof(a) b;将b定义为和a相同的类型

typeof('b') a;将a定义为int 型,'b'自动提升为int

typeof(*a) b;将b定义为a指向的数据的类型

更详细的说明请看gcc编译器说明:http://gcc.gnu.org/onlinedocs/gcc/Typeof.html

2.offsetof

不要认为offsetof是C函数库提供的函数或者宏,我因为这个调了很久的程序……我们也可以在list.h里找到它的定义:

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

((TYPE*)0)->MEMBER是说:将0强制转换成TYPE型的,然后指向了它的成员MEMBER。所以&((TYPE*)0)->MEMBER就可以得到MEMBER在TYPE结构体中的偏移量。比如offset(struct dog,list)得到的结果是12.(为什么不是10呢?这是字节对齐的问题了,可以看看这里: http://blog.sina.com.cn/s/blog_5d9a116e0100r2wh.html

知道了以上两点,理解container_of应该就不成问题了。

container_of里是一个代码块,里面包含两句话。

第一句话定义了一个指向输入参数ptr的指针,typeof(((type*)0)->member)即获得了ptr的类型。为了防止接下来__mptr对源数据的改变,所以定义成了const类型。

第二句话就计算了父结构体的地址。__mptr现在已经存着ptr的地址了,它只要减去list到struct dog的距离就行了。而这个距离已经用offset算好了。

到这里,我们可以写一个test demo了吧:

#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 list_head 
{
	struct list_head *prev,*next;
};
struct dog{
	 char name[10];
	struct list_head list;
};
void main()
{
	struct dog mdog;
	scanf("%s",mdog.name);
	printf("%s\n",container_of(&mdog.list,struct dog,list)->name);
}
下面,我们进入更深一层的讨论:

讨论1.

关于为什么要转换__mptr成(char*),我们可以这样看看:

#include<stdio.h>
#define offsetof(type,member) ((size_t)&((type*)0)->member)
#define char_container_of(ptr, type, member) ({                      \
	const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
	(type *)( (char *)__mptr - offsetof(type,member) );})
#define int_container_of(ptr, type, member) ({                      \
	const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
	(type *)( (int *)__mptr - offsetof(type,member) );})
struct list_head 
{
	struct list_head *prev,*next;
};
struct dog{
	 char name[10];
	struct list_head list;
};
void main()
{
	struct dog mdog;
	printf("&mdog:%u\n",&mdog);
	printf("&mdog.list:%u\n",&mdog.list);
	printf("sizeof(char):%u\n",sizeof(char));
	printf("sizeof(int):%u\n",sizeof(int));
	printf("char_container:%u\n",char_container_of(&mdog.list,struct dog,list));
	printf("int_container:%u\n",int_container_of(&mdog.list,struct dog,list));
}
结果如下:


如果是int指针的话是这样计算的:52-4*12=4;如果是char指针的话就是这样52-1*12=40。

讨论2.

container_of的第一行代码有什么意义?这样实现不是更方便吗:

#define new_container_of(ptr,type,member) ({	\
	(type*)((char*)ptr-offsetof(type,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) );})
#define new_container_of(ptr,type,member) ({	\
	(type*)((char*)ptr-offsetof(type,member));})
struct list_head 
{
	struct list_head *prev,*next;
};
struct dog{
	 char name[10];
	struct list_head list;
};
void main()
{
	struct dog mdog;
	int b;
	scanf("%s",mdog.name);
	printf("%s\n",new_container_of(b,struct dog,list)->name);
	printf("%s\n",container_of(b,struct dog,list)->name);
}

这个程序里new_container_of能顺利编译通过,而container_of会提示不正确的赋值。


讨论3.

受第2个问题的启发,为什么要是

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

而不是

const typeof(*ptr)*__mptr=(ptr);

呢?

这样也能检测上述参数传递是否正确,而且似乎更容易理解。

经过更进一步的测试,我发现"const typeof(*ptr) *__mptr=(ptr);"检测的范围还是没有"const typeof(((type)0)->member)*__mptr=(ptr);"检测的范围广。

比如传进去的是type的二级指针,那么第一种方法将失效。

struct dog ** pdog;
printf("%s\n",container_of(&pdog,struct dog,list)->name);

这样第一种方法编译通过,第二种方法(内核的方法)将得到有用的警告。

仔细想想,确实,"const typeof(*ptr)*__mptr=(ptr);"只能保证mptr和ptr是同类型的指针,至于是member类型的几级指针都没有关系。如果传了二级指针进来,下一条语句里的强制转换"char(*)"也不会检测到使用的是几级指针,这样的话就可能存在着巨大的风险。而内核的实现方法"const typeof( ((type *)0)->member ) *__mptr = (ptr); "一定能够保证传进来的参数是member类型的一级指针!

切记!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值