list_empty(list)) {
__list_splice(list, head);
INIT_LIST_HEAD(list);
}
}
以上函数的功能是将一个链表list的有效信息合并到另外一个链表head后,重新初始化被去掉的空的链表头。这样的描述可能不是太好理解,接下来看看一段代码。
#include
#include
#include "list.h"
typedef struct _stu
{
char name[20];
int num;
struct list_head list;
}stu;
int main()
{
stu *pstu,*pstu2;
stu *tmp_stu;
struct list_head stu_list,stu_list2;
struct list_head *pos;
int i = 0;
INIT_LIST_HEAD(&stu_list);
INIT_LIST_HEAD(&stu_list2);
pstu = malloc(sizeof(stu)*3);
pstu2 = malloc(sizeof(stu)*3);
for(i=0;i<3;i++)
{
sprintf(pstu[i].name,"Stu%d",i+1);
sprintf(pstu2[i].name,"Stu%d",i+4);
pstu[i].num = i+1;
pstu2[i].num = i+4;
list_add( &(pstu[i].list), &stu_list);
list_add( &(pstu2[i].list), &stu_list2);
}
printf("stu_list 链表\n");
list_for_each(pos,&stu_list)
{
tmp_stu = list_entry(pos, stu, list);
printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);
}
printf("stu_list2 链表\n");
list_for_each(pos,&stu_list2)
{
tmp_stu = list_entry(pos, stu, list);
printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);
}
printf("stu_list链表和stu_list2 链表合并以后\n");
list_splice(&stu_list2,&stu_list);
list_for_each(pos,&stu_list)
{
tmp_stu = list_entry(pos, stu, list);
printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);
}
free(pstu);
return 0;
}
运行结果为:
root@ubuntu:/home/paixu/dlist_node# ./a
stu_list 链表
student num: 3 student name: Stu3
student num: 2 student name: Stu2
student num: 1 student name: Stu1
stu_list2 链表
student num: 6 student name: Stu6
student num: 5 student name: Stu5
student num: 4 student name: Stu4
stu_list链表和stu_list2 链表合并以后
student num: 6 student name: Stu6
student num: 5 student name: Stu5
student num: 4 student name: Stu4
student num: 3 student name: Stu3
student num: 2 student name: Stu2
student num: 1 student name: Stu1
有了直观的代码和运行结果,理解起来也更加的容易了。
有了上面的这些操作,但是我们还一直没有讲到我们最终所关心的宿主结构,那么接下来我们一起来看看我们该如何取出宿主结构的指针呢?这也是我认为linux内核双向循环链表实现最为巧妙的地方了。
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
看看上面的代码,发现一个很熟悉的身影(unsigned long)(&((type *)0)->member)),这个我在前一篇博客《C语言的那些小秘密之字节对齐》中已经讲解过了,多以在此就不再做过多的讲解,如果有不明白的读者可以回过去看看讲解再回过来阅读。通过(unsigned long)(&((type *)0)->member))我们得出了成员变量member的偏移量,而ptr为指向member的指针,因为指针类型不同的原因,所以我们再次要先进行(char*)的转换之后再进行计算。所以我们用ptr减去member的偏移量就得到了宿主结构体的指针,这就是一个非常巧妙的地方,这也就使得linux内核双向循环链表能够区别于传统链表的关键所在。可能看到这儿的时候读者已经感觉非常的枯燥了,但是别放弃,坚持看完,因为虽然这样的讲解枯燥了点,但是非常有用。所以坚持坚持吧!
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
#define __list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
#define list_for_each_prev(pos, head)