文章目录
STAILQ 示意图
STAILQ 和 LIST 的不同是,head 里面有个指针 stqh_last,指向最后一个节点的 stqe_next
注意:有的地方也把 STAILQ 叫 simple queue,将接口前缀改为 SIMPLEQ_
接口和实现
先贴代码。(注意:不同版本的代码可能不同)
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type) \
struct name { \
struct type *stqh_first;/* first element */ \
struct type **stqh_last;/* addr of last next element */ \
}
#define STAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).stqh_first }
#define STAILQ_ENTRY(type) \
struct { \
struct type *stqe_next; /* next element */ \
}
/*
* Singly-linked Tail queue functions.
*/
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_NEXT((tqelm), field) = (elm); \
} while (0)
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_FIRST((head)) = (elm); \
} while (0)
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY(head) ? \
NULL : \
((struct type *) \
((char *)((head)->stqh_last) - offsetof(struct type, field))))
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_REMOVE(head, elm, type, field) do { \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD(head, field); \
} \
else { \
struct type *curelm = STAILQ_FIRST((head)); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
if ((STAILQ_NEXT(curelm, field) = \
STAILQ_NEXT(STAILQ_NEXT(curelm, field), field)) == NULL)\
(head)->stqh_last = &STAILQ_NEXT((curelm), field);\
} \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_REMOVE_HEAD_UNTIL(head, elm, field) do { \
if ((STAILQ_FIRST((head)) = STAILQ_NEXT((elm), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
if ((STAILQ_NEXT(elm, field) = \
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
举例
咱们看个例子,例子来自:https://manpages.courier-mta.org/htmlman3/stailq.3.html
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/queue.h>
struct entry {
int data;
STAILQ_ENTRY(entry) entries; /* Singly linked tail queue */
};
STAILQ_HEAD(stailhead, entry);
int
main(void)
{
struct entry *n1, *n2, *n3, *np;
struct stailhead head; /* Singly linked tail queue
head */
STAILQ_INIT(&head); /* Initialize the queue */
n1 = malloc(sizeof(struct entry)); /* Insert at the head */
STAILQ_INSERT_HEAD(&head, n1, entries);
n1 = malloc(sizeof(struct entry)); /* Insert at the tail */
STAILQ_INSERT_TAIL(&head, n1, entries);
n2 = malloc(sizeof(struct entry)); /* Insert after */
STAILQ_INSERT_AFTER(&head, n1, n2, entries);
STAILQ_REMOVE(&head, n2, entry, entries); /* Deletion */
free(n2);
n3 = STAILQ_FIRST(&head);
STAILQ_REMOVE_HEAD(&head, entries); /* Deletion from the head */
free(n3);
n1 = STAILQ_FIRST(&head);
n1->data = 0;
for (int i = 1; i < 5; i++) {
n1 = malloc(sizeof(struct entry));
STAILQ_INSERT_HEAD(&head, n1, entries);
n1->data = i;
}
/* Forward traversal */
STAILQ_FOREACH(np, &head, entries)
printf("%i\n", np->data);
/* TailQ deletion */
n1 = STAILQ_FIRST(&head);
while (n1 != NULL) {
n2 = STAILQ_NEXT(n1, entries);
free(n1);
n1 = n2;
}
STAILQ_INIT(&head);
exit(EXIT_SUCCESS);
}
代码分析
STAILQ_ENTRY 和 STAILQ_HEAD
struct entry {
int data;
STAILQ_ENTRY(entry) entries; /* Singly linked tail queue */
};
STAILQ_HEAD(stailhead, entry);
宏替换后是
struct entry {
int data;
struct {
struct entry *stqe_next;
} entries;
};
struct stailhead {
struct entry *stqh_first;
struct entry **stqh_last;
};
其实和 SLIST 差不多,唯一的不同是第 10 行,多了一个指向尾节点的指针(准确地说是指向尾节点的 stqe_next)
STAILQ_INIT 和 STAILQ_INSERT_HEAD
struct entry *n1, *n2, *n3, *np;
struct stailhead head; /* Singly linked tail queue
head */
STAILQ_INIT(&head); /* Initialize the queue */
n1 = malloc(sizeof(struct entry)); /* Insert at the head */
STAILQ_INSERT_HEAD(&head, n1, entries);
5:宏展开是
(&head)->stqh_first = ((void *)0);
(&head)->stqh_last = &(&head)->stqh_first;
链表的初始化为空,stqh_first 等于 NULL,stqh_last 指向自己的 stqh_first
8:宏展开是
if (((n1)->entries.stqe_next = (&head)->stqh_first) == ((void *)0))
(&head)->stqh_last = &(n1)->entries.stqe_next;
(&head)->stqh_first = (n1);
把 n1 插入链表的第一个位置,会影响哪个指针的值呢?
- stqh_first
- n1 的 stqe_next
- 如果链表为空,那么插入 n1 后,stqh_last 也要改变
第 1 行解决了 2
第 2 行解决了 3
第 3 行解决了 1
STAILQ_INSERT_TAIL
n1 = malloc(sizeof(struct entry)); /* Insert at the tail */
STAILQ_INSERT_TAIL(&head, n1, entries);
2:宏替换是
(n1)->entries.stqe_next = ((void *)0);
*(&head)->stqh_last = (n1);
(&head)->stqh_last = &(n1)->entries.stqe_next;
把 n1 插入链表的最后一个位置,会影响哪些指针的值呢?
- stqh_last
- n1 的 stqe_next,需要为 NULL
- 原先的最后一个节点(假设是 n8)的 stqe_next
第 1 行解决了 2
第 2 行有点麻烦,(&head)->stqh_last 指向 n8 节点的 stqe_next,*(&head)->stqh_last 就是 n8 节点的 stqe_next,赋值 n1 给它,解决了 3
第 3 行解决了 1
STAILQ_INSERT_AFTER
n2 = malloc(sizeof(struct entry)); /* Insert after */
STAILQ_INSERT_AFTER(&head, n1, n2, entries);
第 2 行的意思是把 n2 插入到 n1 的后面
代码展开后是
if (((n2)->entries.stqe_next = (n1)->entries.stqe_next) == ((void *)0))
(&head)->stqh_last = &(n2)->entries.stqe_next;
(n1)->entries.stqe_next = (n2);
把 n2 插入到 n1 的后面,会影响哪些指针的值呢?
- n1 的 stqe_next
- n2 的 stqe_next
- 如果 n2 是最后一个节点,还要修改 (&head)->stqh_last
第 1 行的 (n2)->entries.stqe_next = (n1)->entries.stqe_next)
解决了 2
第 3 行解决了 1
第 2 行解决了 3
STAILQ_REMOVE
STAILQ_REMOVE(&head, n2, entry, entries); /* Deletion */
free(n2);
第 1 行展开是,有点长
if ((&head)->stqh_first == (n2)) {
if ((((&head))->stqh_first = ((&head))->stqh_first->entries.stqe_next) == ((void *)0))
((&head))->stqh_last = &((&head))->stqh_first;
} else {
struct entry *curelm = (&head)->stqh_first;
while (curelm->entries.stqe_next != (n2))
curelm = curelm->entries.stqe_next;
if ((curelm->entries.stqe_next = curelm->entries.stqe_next->entries.stqe_next) == ((void *)0))
(&head)->stqh_last = &(curelm)->entries.stqe_next;
}
分 2 种情况。n2 是不是第一个节点(第 1 行代码),如果是,需要修改的指针有
- (&head)->stqh_first
- 如果 n2 是仅有的一个节点,那么还需要修改 (&head))->stqh_last
第 2 行的前半句解决了 1
第 3 行解决了 2
如果 n2 不是第一个节点,那就执行 5-10,先查找 n2 (6-7 行),当找到的时候, curelm 代表 n2 前面的那个节点。此时需要修改的指针有:
- curelm 的 stqe_next
- 如果 n2 是最后一个节点,删除它后需要修改 (&head))->stqh_last
第 8 行的前半句解决了 1
第 9 行解决了 2
STAILQ_FIRST 和 STAILQ_REMOVE_HEAD
n3 = STAILQ_FIRST(&head);
STAILQ_REMOVE_HEAD(&head, entries); /* Deletion from the head */
free(n3);
STAILQ_FIRST 比较简单,就是取链表的第一个节点,注意,不是删除
第 1 行展开:n3 = ((&head)->stqh_first);
STAILQ_REMOVE_HEAD 是删除第一个节点
第 2 行展开:
if (((&head)->stqh_first = (&head)->stqh_first->entries.stqe_next) == ((void *)0))
(&head)->stqh_last = &(&head)->stqh_first;
如果删除之前,仅有一个节点,那么需要修改 stqh_last,也就是第 2 行
如果有多个节点,那么只需要修改 stqh_first(第 1 行的前半句)
STAILQ_FOREACH
/* Forward traversal */
STAILQ_FOREACH(np, &head, entries)
printf("%i\n", np->data);
STAILQ_FOREACH(np, &head, entries) 用来遍历链表的每个节点。第一个参数是临时变量,指向当前的节点,第二个参数是表头的地址,第三个 entries 是无标签结构体的成员名。
第 2 行展开:
for ((np) = ((&head)->stqh_first); (np); (np) = ((np)->entries.stqe_next))
printf("%i\n", np->data);
典型的遍历。注意,这种遍历是不能删除的,因为如果把 np 指向的节点删除了,
(np)->entries.stqe_next
这句就不对了。
STAILQ_NEXT
/* TailQ deletion */
n1 = STAILQ_FIRST(&head);
while (n1 != NULL) {
n2 = STAILQ_NEXT(n1, entries);
free(n1);
n1 = n2;
}
STAILQ_NEXT(n1, entries) 表示取 n1 的下一个节点
第 4 行展开是 n2 = ((n1)->entries.stqe_next);
比较简单。
这段代码也展示了如何删除整个链表。
还有几个宏,例子里面没有用到,我们也看一下。
STAILQ_EMPTY
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
这个很好理解,不解释了。
STAILQ_LAST
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY(head) ? \
NULL : \
((struct type *) \
((char *)((head)->stqh_last) - offsetof(struct type, field))))
注意,我的 Linux 操作系统上,并不包含这个宏,所以,如果要用,需要手工把这段代码包含进去。
这个宏的意思找到队列的最后一个节点。它是这么用的:
n1 = STAILQ_LAST(&head, entry, entries);
本文开始的时候,有
struct entry {
int data;
STAILQ_ENTRY(entry) entries; /* Singly linked tail queue */
};
STAILQ_HEAD(stailhead, entry);
n1 = STAILQ_LAST(&head, entry, entries)
的后两个参数要对应第 3 行的定义。
我们回头看 STAILQ_LAST 的代码
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY(head) ? \
NULL : \
((struct type *) \
((char *)((head)->stqh_last) - offsetof(struct type, field))))
如果链表为空,就返回 NULL,如果不为空呢?当然是顺着 (head)->stqh_last 找对最后一个节点。
问题是,(head)->stqh_last 指向的是最后一个节点的 entries.stqe_next 域
struct entry {
int data;
struct {
struct entry *stqe_next;
} entries;
};
也就是第 4 行,(head)->stqh_last 的值是 struct entry *stqe_next 的地址,而不是 struct entry 的地址,所以需要转换一下。转换需要用到
offsetof(struct type, field)
这也是一个宏,代码大概在 /usr/src/linux-headers-4.8.0-36-generic/include/linux/stddef.h
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
这里的 TYPE 表示某个结构体类型,MEMBER 表示结构体中的一个成员,这个宏求出了成员在结构体中的位置偏移(以字节为单位)
假设一个结构体变量(类型是 struct entry)的起始地址是 0x1234,它里面有个成员是 data,请问 data 变量的地址是多少?
可以这样求:
&((struct entry *)0x1234)->data
->
比类型转换的优先级高,所以要加括号。((struct entry *)0x1234)->data
就得到了成员,再取地址,得到成员的地址,再来一个类型转换,转成无符号整形:
(size_t)&((struct entry *)0x1234)->data
终于得到成员的起始地址了,这时候我们看看它对于结构体变量的起始地址的偏移是多少?也就是用成员的地址减去结构体变量的地址:
(size_t)&((struct entry *)0x1234)->data - 0x1234
不难看出,这里的 0x1234 换成什么数字都可以,因为偏移是一个相对量,和起始地址无关
为了简单,那就把 0x1234 换成 0 吧。
我们做一般化的处理,把 struct entry 叫 TYPE,把 data 叫 MEMBER,所以就有了
((size_t)&((TYPE *)0)->MEMBER)
咱们回到正题上来,(head)->stqh_last 的值是 struct entry *stqe_next 的地址,而不是 struct entry 的地址,*如果我们知道 struct entry stqe_next 相对于 struct entry 的偏移 x,那么用 (head)->stqh_last 减去 x 就可以了。
利用 offsetof(struct type, field)
来求偏移,同时注意到 struct entry *stqe_next 和 entries 是同一个地址,所以偏移是:offsetof(struct entry , entries )
最后一个节点的地址是:
(struct entry *)((char*)((head)->stqh_last) - offsetof(struct entry, entries)))
把上面这个式子一般化,entry 换成 type,entries 换成 field,就得到了代码中的:
((struct type *) \
((char *)((head)->stqh_last) - offsetof(struct type, field))))
其实还有更简单的方法,直接利用内核里面的 container_of
宏,这里只做个提示,就不展开了。
【end】