链表之TAILQ

Posted 车子 chezi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表之TAILQ相关的知识,希望对你有一定的参考价值。

文章目录

TAILQ 介绍

TAILQ 队列是 FreeBSD 内核中的一种队列数据结构,在一些著名的开源库中(如 DPDKlibevent)有广泛的应用。

TAILQ 和Linuxlist的组织方式不一样,后者是单纯地将 struct list_head 作为链表的挂接点,并没有用户的信息,它们的差别如下图。

Linux中的list只将struct list_head作为用户元素的挂接点,因此在正向遍历链表时,需要使用container_of这类接口才能获取用户的数据,而TAILQ由于tqe_next指针直接指向用户元素,所以理论上,正向遍历TAILQlist更快.

头文件

头文件来自我最近看的项目: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_laststruct entry **tqe_prev 看起来有点诡异,为什么是二级指针?

改成一级的行不行?

如果把第 5 行改成 struct entry *tqe_prev,肯定不行,如果它的前面是头节点怎么办,头节点的定义里面没有 struct entry 啊!

再仔细看看,不管是头节点还是普通节点,都有 struct entry * 这个类型,那就定义一个类型为 struct entry ** 的变量好了。这样前插的时候就可以统一处理了,例子见 TAILQ_INSERT_BEFORE

总结一下,定义 TAILQ 的时候,有 3 个基本要素:

  1. 结构体 struct entry:TAILQ_ENTRY() 的参数和 TAILQ_HEAD() 的第二个参数

  2. 结构体 struct tailhead:TAILQ_HEAD() 的第一个参数

  3. 变量 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 插入到队列的第一个位置,需要修改哪些指针呢?

  1. n1 的 entries.tqe_next
  2. n1 的 entries.tqe_prev
  3. (&head))->tqh_first
  4. 之前第一个节点的 entries.tqe_prev
  5. 如果 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 插入到队列的末尾,需要修改哪些指针呢?

  1. (&head)->tqh_last
  2. n1 的 entries.tqe_next 应该为 NULL
  3. n1 的 entries.tqe_prev
  4. 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 的后面,需要修改哪些指针呢?

  1. n1 的 entries.tqe_next
  2. n2 的 entries.tqe_prev
  3. n2 的 entries.tqe_next
  4. 插入 n2 之前,如果 n1 后面有个 n3,则需要修改 n3 的 entries.tqe_prev;
  5. 插入 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的主要内容,如果未能解决你的问题,请参考以下文章

链表之TAILQ

链表之STAILQ

链表之STAILQ

链表之STAILQ

第二百八十节,MySQL数据库-外键链表之一对多

数据结构与算法-线性表之静态链表