()函数的实现我们接下来当然也要看看list_add()和list_add_tail()的实现。
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
看了上面的实现方式我们知道他们都是调用底层的__list_add()来实现的。看看在__list_add()函数里面传递不同的参数我们就能实现不同的添加节点的方法。__list_add()函数前面的双划线通常表示这是一个底层函数,供其他的模块调用。
第一个list_add()传递的参数实现的是在head和head->next两指针所指向的结点之间插入new所指向的结点。即就是在head指针的后面插入一个new所指向的结点。Head并非一定为头结点。如果我们的链表只含有一个头节点时,上面的__list_add(new, head, head->next)仍然成立。
第二个list_add_tail()其功能是在结点指针head所指向结点的前面插入new所指向的结点。当如果head指向的是头结点,那么就相当于在尾结点后面增加一个new所指向的结点。在这个函数里值得注意的是head->prev不能为空,如果head为头结点,那么head->prev要指向一个数值,一般为指向尾结点,构成循环链表。
说到这儿可能有的读者已经迫不及待的想看看代码了,那我们就用linux内核里的list.h在应用层来写出我们的代码。
#include
#include
#include "list.h"
typedef struct _stu
{
char name[20];
int num;
struct list_head list;
}stu;
int main()
{
stu *pstu;
stu *tmp_stu;
struct list_head stu_list;
struct list_head *pos;
int i = 0;
INIT_LIST_HEAD(&stu_list);
pstu = malloc(sizeof(stu)*5);
for(i=0;i<5;i++)
{
sprintf(pstu[i].name,"Stu%d",i+1);
pstu[i].num = i+1;
list_add( &(pstu[i].list), &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
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
看看上面的代码,我们做的基本工作都有那些呢?
1、定义了一个宿主结构体stu,并且在宿主结构体中我们定义了一个struct list_head 类型的list变量;
2、定义一个头结点并且对其进行初始化工作;
3、对定义的一个宿主结构体变量申请内存空间;
4、对申请的宿主结构体变量初始化和添加到以stu_list为头结点的链表中。
在上面值得注意的就是list_for_each()和list_entry(),我们会在接下来的部分讲解,读者在这儿只需要知道它们两个在此合在一起的作用就是打印出宿主结构stu中每个数据。sprintf()的使用就不在这里讲解了,很简单,相信读者猜都可以猜出它的功能。读者如果一开始对上面的文字描述部分有什么疑惑或者不解的现在看了代码的实现应该都懂了,list_add_tail()的使用和list_add()类似,读者可以自己修改代码实现。如果一开始对于list_add()不太理解的读者,现在对于list_add()的理解现在可以参考运行结果和上面的文字描述部分。
我们接着往下看。
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
在prev和next指针所指向的结点之间,两者互相所指。其实也就是prev为待删除的结点的前面一个结点,next为待删除的结点的后面一个结点。
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
删除entry所指的结点,同时将entry所指向的结点指针域封死。在这里值得注意的是LIST_POISON1、LIST_POISON2。它们在list.h中的宏定义如下:
#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200200)
对LIST_POISON1、LIST_POISON2的说明,Linux 内核中有这么一句话:These are non-NULL pointers that will result in page faults under normal circumstances,used to verify that nobody uses non-initialized list entries。也就是说它们并不是空指针,但是访问这样的指针在正常情况下是会导致出错的。其实按照我们一般的思路都是把entry->next 和entry->prev 赋值为NULL,使得不可以通过该节点进行访问。但是在