链表之TAILQ
Posted 车子 chezi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表之TAILQ相关的知识,希望对你有一定的参考价值。
文章目录
TAILQ 介绍
TAILQ
队列是 FreeBSD
内核中的一种队列数据结构,在一些著名的开源库中(如 DPDK
、libevent
)有广泛的应用。
TAILQ 和Linux
中list
的组织方式不一样,后者是单纯地将 struct list_head
作为链表的挂接点,并没有用户的信息,它们的差别如下图。
Linux
中的list
只将struct list_head
作为用户元素的挂接点,因此在正向遍历链表时,需要使用container_of
这类接口才能获取用户的数据,而TAILQ
由于tqe_next
指针直接指向用户元素,所以理论上,正向遍历TAILQ
比list
更快.
头文件
头文件来自我最近看的项目:apache-mynewt-core-1.9.0\\sys\\sys\\include\\sys\\queue.h
仅截取部分。
// "bsd_queue.h" 为了测试,我重新起了名字
/*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* $FreeBSD: src/sys/sys/queue.h,v 1.32.2.7 2002/04/17 14:21:02 des Exp $
*/
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type) \\
struct name \\
struct type *tqh_first; /* first element */ \\
struct type **tqh_last; /* addr of last next element */ \\
#define TAILQ_HEAD_INITIALIZER(head) \\
NULL, &(head).tqh_first
#define TAILQ_ENTRY(type) \\
struct \\
struct type *tqe_next; /* next element */ \\
struct type **tqe_prev; /* address of previous next element */ \\
/*
* Tail queue functions.
*/
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_FOREACH(var, head, field) \\
for ((var) = TAILQ_FIRST((head)); \\
(var); \\
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \\
for ((var) = TAILQ_LAST((head), headname); \\
(var); \\
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_INIT(head) do \\
TAILQ_FIRST((head)) = NULL; \\
(head)->tqh_last = &TAILQ_FIRST((head)); \\
while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do \\
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\\
TAILQ_NEXT((elm), field)->field.tqe_prev = \\
&TAILQ_NEXT((elm), field); \\
else \\
(head)->tqh_last = &TAILQ_NEXT((elm), field); \\
TAILQ_NEXT((listelm), field) = (elm); \\
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \\
while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do \\
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \\
TAILQ_NEXT((elm), field) = (listelm); \\
*(listelm)->field.tqe_prev = (elm); \\
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \\
while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do \\
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \\
TAILQ_FIRST((head))->field.tqe_prev = \\
&TAILQ_NEXT((elm), field); \\
else \\
(head)->tqh_last = &TAILQ_NEXT((elm), field); \\
TAILQ_FIRST((head)) = (elm); \\
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \\
while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do \\
TAILQ_NEXT((elm), field) = NULL; \\
(elm)->field.tqe_prev = (head)->tqh_last; \\
*(head)->tqh_last = (elm); \\
(head)->tqh_last = &TAILQ_NEXT((elm), field); \\
while (0)
#define TAILQ_LAST(head, headname) \\
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \\
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_REMOVE(head, elm, field) do \\
if ((TAILQ_NEXT((elm), field)) != NULL) \\
TAILQ_NEXT((elm), field)->field.tqe_prev = \\
(elm)->field.tqe_prev; \\
else \\
(head)->tqh_last = (elm)->field.tqe_prev; \\
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \\
while (0)
举例
来自 https://manpages.courier-mta.org/htmlman3/tailq.3.html
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "bsd_queue.h" //没有用 Linux 的,换成了我自己的
struct entry
int data;
TAILQ_ENTRY(entry) entries; /* Tail queue */
;
TAILQ_HEAD(tailhead, entry);
int
main(void)
struct entry *n1, *n2, *n3, *np;
struct tailhead head; /* Tail queue head */
int i;
TAILQ_INIT(&head); /* Initialize the queue */
n1 = malloc(sizeof(struct entry)); /* Insert at the head */
TAILQ_INSERT_HEAD(&head, n1, entries);
n1 = malloc(sizeof(struct entry)); /* Insert at the tail */
TAILQ_INSERT_TAIL(&head, n1, entries);
n2 = malloc(sizeof(struct entry)); /* Insert after */
TAILQ_INSERT_AFTER(&head, n1, n2, entries);
n3 = malloc(sizeof(struct entry)); /* Insert before */
TAILQ_INSERT_BEFORE(n2, n3, entries);
TAILQ_REMOVE(&head, n2, entries); /* Deletion */
free(n2);
/* Forward traversal */
i = 0;
TAILQ_FOREACH(np, &head, entries)
np->data = i++;
/* Reverse traversal */
TAILQ_FOREACH_REVERSE(np, &head, tailhead, entries)
printf("%i\\n", np->data);
/* TailQ deletion */
n1 = TAILQ_FIRST(&head);
while (n1 != NULL)
n2 = TAILQ_NEXT(n1, entries);
free(n1);
n1 = n2;
TAILQ_INIT(&head);
exit(EXIT_SUCCESS);
代码分析
TAILQ_ENTRY 和 TAILQ_HEAD
struct entry
int data;
TAILQ_ENTRY(entry) entries; /* Tail queue */
;
TAILQ_HEAD(tailhead, entry);
TAILQ_ENTRY(entry) entries
中的 entry 由用户指定,是外层大结构体的标签,宏展开后就是下面的第 1,4,5 行中的 entry;entries 也由用户指定,是无标签结构体的成员名,宏展开后是下面第 6 行中的 entries
struct entry
int data;
struct
struct entry *tqe_next;
struct entry **tqe_prev;
entries;
;
struct tailhead
struct entry *tqh_first;
struct entry **tqh_last;
;
TAILQ_HEAD(tailhead, entry)
中的 tailhead 由用户指定,是表头结构体的标签,如上面第 9 行的 tailhead,entry 要和 TAILQ_ENTRY
中的第一个参数保持一致。
struct entry **tqh_last
和 struct entry **tqe_prev
看起来有点诡异,为什么是二级指针?
改成一级的行不行?
如果把第 5 行改成 struct entry *tqe_prev,肯定不行,如果它的前面是头节点怎么办,头节点的定义里面没有 struct entry 啊!
再仔细看看,不管是头节点还是普通节点,都有 struct entry * 这个类型,那就定义一个类型为 struct entry ** 的变量好了。这样前插的时候就可以统一处理了,例子见 TAILQ_INSERT_BEFORE
总结一下,定义 TAILQ 的时候,有 3 个基本要素:
-
结构体 struct entry:TAILQ_ENTRY() 的参数和 TAILQ_HEAD() 的第二个参数
-
结构体 struct tailhead:TAILQ_HEAD() 的第一个参数
-
变量 entries:紧跟在 TAILQ_ENTRY() 后面
TAILQ_INIT
struct entry *n1, *n2, *n3, *np;
struct tailhead head; /* Tail queue head */
int i;
TAILQ_INIT(&head); /* Initialize the queue */
第 5 行,宏展开是
(((&head))->tqh_first) = ((void *)0);
(&head)->tqh_last = &(((&head))->tqh_first);
意思是表头的 tqh_first 为 NULL,tqh_last 指向自己的 tqh_first
TAILQ_INSERT_HEAD
n1 = malloc(sizeof(struct entry)); /* Insert at the head */
TAILQ_INSERT_HEAD(&head, n1, entries);
2:宏展开
if (((((n1))->entries.tqe_next) = (((&head))->tqh_first)) != ((void *)0))
(((&head))->tqh_first)->entries.tqe_prev = &(((n1))->entries.tqe_next);
else
(&head)->tqh_last = &(((n1))->entries.tqe_next);
(((&head))->tqh_first) = (n1);
(n1)->entries.tqe_prev = &(((&head))->tqh_first);
要把 n1 插入到队列的第一个位置,需要修改哪些指针呢?
- n1 的 entries.tqe_next
- n1 的 entries.tqe_prev
- (&head))->tqh_first
- 之前第一个节点的 entries.tqe_prev
- 如果 n1 是唯一的节点,还需要修改 (&head)->tqh_last
好,我们看代码是如何解决的。
第 1 行解决了 1
第 2 行解决了 4,这个要解释一下,(((&head))->tqh_first)->entries.tqe_prev 表示插入 n1 之前的第一个节点的 tqe_prev,插入后,它应该指向 n1 的 tqe_next
第 4 行解决了 5
第 6 行解决了 3
第 7 行解决了 2
TAILQ_INSERT_TAIL
n1 = malloc(sizeof(struct entry)); /* Insert at the tail */
TAILQ_INSERT_TAIL(&head, n1, entries);
把 n1 插入到队列的末尾,需要修改哪些指针呢?
- (&head)->tqh_last
- n1 的 entries.tqe_next 应该为 NULL
- n1 的 entries.tqe_prev
- n1 插入之前最后一个节点(叫 n0 吧)的 entries.tqe_next
第 2 行,展开宏
(((n1))->entries.tqe_next) = ((void *)0);
(n1)->entries.tqe_prev = (&head)->tqh_last;
*(&head)->tqh_last = (n1);
(&head)->tqh_last = &(((n1))->entries.tqe_next);
第 1 行解决了 2
第 2 行解决了 3
第 3 行解决了 4,因为插入之前 (&head)->tqh_last 指向 n0 的 entries.tqe_next,对 (&head)->tqh_last 解引用,就得到变量 n0 的 entries.tqe_next,它应该等于 n1
第 4 行解决了 1
TAILQ_INSERT_AFTER
n2 = malloc(sizeof(struct entry)); /* Insert after */
TAILQ_INSERT_AFTER(&head, n1, n2, entries);
把 n2 插入到 n1 的后面,需要修改哪些指针呢?
- n1 的 entries.tqe_next
- n2 的 entries.tqe_prev
- n2 的 entries.tqe_next
- 插入 n2 之前,如果 n1 后面有个 n3,则需要修改 n3 的 entries.tqe_prev;
- 插入 n2 之前,如果 n1 是最后一个节点,则要修改 (&head)->tqh_last
第 2 行宏展开:
if (((((n2))->entries.tqe_next) = (((n1))->entries.tqe_next)) != ((void *)0))
(((n2))->entries.tqe_next)->entries.tqe_prev = &(((n2))->entries.tqe_next);
else
(&head)->tqh_last = &(((n2))->entries.tqe_next);
(((n1))->entries.tqe_next) = (n2);
(n2)->entries.tqe_prev = &(((n1))->entries.tqe_next);
第 1 行解决了 3
第 2 行解决了 4
第 4 行解决了 5
第 6 行解决了1
第 7 行解决了 2
TAILQ_INSERT_BEFORE
n3 = malloc(sizeof(struct entry)); /* Insert before */
TAILQ以上是关于链表之TAILQ的主要内容,如果未能解决你的问题,请参考以下文章