在学习数据结构课时,我们知道链表元素是个结构体,由数据项和指针项构成,正式里面的指针项是形成链表结构的核心,但数据项才是链表有意义的依托,如果一个链表元素只有指针项,没有数据项,这个链表是没有意义的。但这只是表面如此。在学习linux内核的双向循环链表中,我们不得不叹服内核设计者的匠心独具。List_head 结构定义在include/linux/types.h中
struct list_head {
struct list_head *next, *prev;
};
这个结构体没有数据项!也就是说,n多个这种元素可以首尾相连形成一个双向循环的环,但是在这个链表中没有信息的载体。那么一切链表的操作,比如遍历,在这里有什么意义呢?总不能就在一堆指针里闲逛吧。
我们要让链表有意义,必须使链表有信息的载体,即数据项,这是毋庸置疑的。那就让我们换个思路,既然一堆list_head已经构成一个链表,而且我们不能往list_head对象里面加入数据项,那么让这些list_head对象位于另外一个结构体的内部,作为指针域存在,这个外部结构体的其他成员作为数据项存在,是否就让链表有意义了呢。
答案是肯定的,但是问题又来了,N多外部结构体的对象通过包含list_head相互联系起来,可是在对链表进行操作时,我们的目的是要操作或访问与之相关的数据。而现在数据是存在于List_head对象外的,我们不能通过链表元素的指针来访问。事实上,我们只能通过同时包含这些数据项和链表元素的外部结构体对象的指针来访问,为什么呢?哈哈,绕晕了,因为这些数据本来就是这些外部结构体对象的内容之一啊。
现在问题归结为:已知一个结构体里某个成员的指针,怎么得到这个结构体的指针?
试想,如果知道了这个已知成员在结构体里的地址偏移,我们就能得到这个结构体的指针。如是List_head类型的链表的操作才显得有意义。
到现在所有的问题都指向了一个宏,container_of(),这个宏才是整个链表结构的最本质的地方。该宏定义在kernel.h中。
/**
* 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) );})
其中offsetof的定义为:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
offsetof宏的作用是返回一个TYPE类型结构的MEMBER成员在该结构中的编译。把零地址强制转换一个指向该结构体对象的指针,那么该对象中MEMBER成员的地址就是该成员在这个结构体中的偏移。
那么container_of的工作原理就相当简单了,就是用MEMBER的地址减去上一步中得到的偏移,即为TYPE类型的结构体对象的地址。
为什么__mptr要强制转换为char*呢?这是c语言的基础知识,指针的加减对应到地址是根据指针所指的数的类型来确定的。如果MEMBER是int,而不把它的指针强制为char*,那么减去的将是4倍的地址偏移,当然是不对的。
既然内核中链表如此精妙,我们是否可以将其纳为己用呢,当然可以,只要我们实现了那个container_of宏就行。事实上,这是相当简单的,只需一个定义:
#define container_of(ptr, type, member) (type *)( (char *)ptr - offsetof(type,member) )就可以了。Vc里预定义了offsetof宏,其语义跟linux内核相同。
在VC上的测试:
#define container_of(ptr, type, member) (type *)( (char *)ptr - offsetof(type,member) )
#include<iostream>
using namespace std;
int main()
{
struct StructA
{
int a1;
int a2;
};
struct StructB
{
int b1;
int b2;
StructA a;
};
struct StructC
{
int c1;
int c2;
StructB b;
};
StructA aa;
aa.a1=11;
aa.a2=22;
StructB bb;
bb.b1=33;
bb.b2=44;
bb.a=aa;
StructC cc;
cc.c1=55;
cc.c2=66;
cc.b=bb;
StructA *pa=container_of(&aa.a1,StructA,a1);
cout<<pa->a1<<" "<<pa->a2<<endl;
StructB *pb=container_of(&bb.b2,StructB,b2);
cout<<pb->b1<<" "<<pb->b2<<" "<<(pb->a).a1<<" "<<(pb->a).a2<<endl;
StructB *pb2=container_of(pa,StructB,a);
cout<<pb->b1<<" "<<pb->b2<<" "<<(pb->a).a1<<" "<<(pb->a).a2<<endl;
StructC *pc=container_of(&cc.c1,StructC,c1);
cout<<pc->c1<<" "<<pc->c2<<" "<<(pc->b).b1<<" "<<(pc->b).b2<<" "<<(pc->b).a.a1<<" "<<(pc->b).a.a2<<endl;
StructC *pc2=container_of(pb,StructC,b);
cout<<pc->c1<<" "<<pc->c2<<" "<<(pc->b).b1<<" "<<(pc->b).b2<<" "<<(pc->b).a.a1<<" "<<(pc->b).a.a2<<endl;
}
运行结果为:
相当舒适啊。从此,我们可以把List_head和list.h中所有对链表的操作当做自己的东西了。
文章评论(0条评论)
登录后参与讨论