quicklist实现了一个双向循环链表。文件中也有提及,它是参照linux 2.4.3中的list.h设计的。这一设计主要有两个特点:
1. 使用了宿主 机制,提高了代码的通用性,详见下述数据结构说明;
2. 使用了大量的内联 函数和宏 定义,目的是简化部分代码书写,减小运行时的函数调用开销,详见下述功能函数说明。
只有一个双向链表节点:
struct qlist_head { struct qlist_head *next, *prev; };
不包含链表项是为了提高通用性,不然需要为每一种不同类型的链表项重复所有的功能函数,因为C语言没有模板或者装箱等通用机制(当然,可以考虑使用void指针来提高通用性,参见llist )。
这种链表使用时需要宿主结构体 ,才能产生实际作用,也就是说能通过链表节点,获得我们附加的数据。传统实现方式就是将这些附加信息直接写到如上节点结构体中,而通用实现方式下,则是写到用户自定义的宿主结构体中。例如:
struct entry { struct qlist_head qlist_node; // 其他所需数据,如一个整型 int node_value; };
使用时,调用如下宏定义:
#define qlist_entry(ptr, type, member) / ((type *)((char *)(ptr)-(unsigned long)((&((type *)0)->member))))
其中,ptr是链表节点的指针,type是宿主结构体的类型,member是链表节点在宿主结构体中的名称;(&((type *)0)->member)表示宿主结构地址为0时链表节点的地址,即链表节点在宿主结构中的偏移量。本例中获得宿主结构体地址的调用代码是:
some_entry = qlist_entry(qlist_head_p, struct entry, qlist_node);
进而可以获得该节点的附加信息some_entry->node_value。
所有功能函数都作为内联函数写在.h文件中。原有C语言是没有内联机制的,也没有“inline”的保留字。在GCC中,添加了内联的私有扩展,为了防止和已有代码冲突,使用“__inline__”作为声明内联时的标识。
- 初始化
#define QLIST_HEAD_INIT(name) { &(name), &(name) } #define QLIST_HEAD(name) / struct qlist_head name = QLIST_HEAD_INIT(name)
其中name是链表名,在QLIST_HEAD宏中声明了名为name的链表头,并调用QLIST_HEAD_INIT宏获得自身的地址分别赋值给链表头的next和prev指针,构成一个空链表。这一过程也可通过如下宏实现:
#define INIT_QLIST_HEAD(ptr) do { / (ptr)->next = (ptr); (ptr)->prev = (ptr); / } while (0)
加上一层do-while“外套”是为了保证在多种上下文环境中,该宏替换不会引起歧义或编译错误,如
if (ptr != NULL) INIT_QLIST_HEAD(ptr); else QLIST_HEAD(qlist_name);
不使用do-while形式的宏定义就会发生问题。
【注意】该双向循环链表的链表头不包含在宿主中。
- static __inline__ void __qlist_del(struct qlist_head * prev, struct qlist_head * next); static __inline__ void qlist_del(struct qlist_head *entry);
【注意】删除操作只是将目标节点从链表中脱离,并不包含释放为该节点分配的内存空间等操作。
- static __inline__ struct qlist_head* qlist_pop(struct qlist_head *head);
弹出链表第一项(链表头之后的物理节点)。
该函数与在链表头后插入新项的函数
static __inline__ void qlist_add(struct qlist_head *new, struct qlist_head *head)
配合,可以实现栈(stack)。
- static __inline__ void qlist_splice(struct qlist_head *qlist, struct qlist_head *head)
合并两个链表,将前者(参数qlist)插入到后者的某个位置(参数head),即参数head既可以是链表头(相当于第二个链表接到第一个链表的后面),也可以是链表中的某个项(相当于第二个链表在此分开,插入第一个链表)。
【注意】调用此函数后,如果第一个链表qlist的链表头是占用堆空间的,那么需要手工将其释放。
- #define qlist_for_each(pos, head) / for (pos = (head)->next; pos != (head); pos = pos->next) #define qlist_for_each_safe(pos, scratch, head) / for (pos = (head)->next, scratch = pos->next; pos != (head);/ pos = scratch, scratch = pos->next)
为方便书写遍历代码定义的宏。后面所谓“安全”遍历,意思是指针pos可以被释放,而不影响继续迭代,因为pos的下一项已经预存在了scratch中。
- static inline struct qlist_head * qlist_find( struct qlist_head *list, int (*compare)(struct qlist_head *, void *), void *ptr)
查找链表中第一个符合条件的链表节点,满足的条件是(*compare) (pos, ptr)等于非零 ,其中pos是当前遍历到的链表节点指针。
- 其余函数略