TAILQ链表队列详解

这几天在阅读qemu内存模型相关代码,发现在MemoryRegion结构体中使用了QTAIL_HEAD和QTAIL_ENTRY两个宏定义进行队列(链表)定义,然后使用QTAILQ_FOREACH,QTAILQ_INSERT_BEFORE,QTAILQ_INSERT_TAIL进行队列(链表)操作。其实在qemu源代码中好多处都用到了以上相关宏定义,今天花时间研究了一波这几个相关的宏定义,简单记录一下。

先撇开qemu相关知识,就仅仅对以上几个宏定义进行分析
TAILQ相关的宏定义主要有如下几个:

TAILQ_HEAD 定义队列头
TAILQ_ENTRY 队列实体定义
TAILQ_INIT 初始化队列
TAILQ_FOREACH 对队列进行遍历操作
TAILQ_INSERT_BEFORE 在指定元素之前插入元素
TAILQ_INSERT_TAIL 在队列尾部插入元素
TAILQ_EMPTY 检查队列是否为空
TAILQ_REMOVE 从队列中移除元素

为了更好理解,通过直接用以上宏创建一个链表队列来对上述宏进行讲解。
(1)为了实现一个链表队列,首先我们得定义一个结构体作为链表队列结点,结构体定义如下:

typedef struct Node {
    /*some attr*/
    int id;
    
    /*link*/
    TAILQ_ENTRY(Node) link;
} Node;

上面结构体定义中我们使用了 TAILQ_ENTRY 来定义结点间的链接,TAILQ_ENTRY宏定义如下:

#define TAILQ_ENTRY(type)                                              \
struct {                                                                \
        type *tqe_next;       /* next element */                        \
        type **tqe_prev;      /* address of previous next element */    \
}

将TAILQ_ENTRY宏定义在我们定义的Node结构体中展开如下:

    typedef struct Node {
        /*some attr*/
        int id;

        /*link*/
        struct { 
            Node *tqe_next; 
            Node **tqe_prev; 
         } link;
    } Node;

在link属性中包含了两个元素,tqe_next和tqe_prev,其中tqe_next指向下一个Node结点,但是tqe_pre却不是指向前一个node结点(刚开始看这些代码的时候一直以为tqe_prev是指向前一个结点的,最终分析代码始终感觉怪怪的才发现tqe_pre不是指向前一个结点,其实早就应该知道tqe_pre是一个二级指针,肯定有其他用途,不应认为指向前面一个结点,真实太蠢了),tqe_pre是指向前一个结点的link.tqe_next属性,所以它才会是一个二级指针的形式。直接上图,假设我们已经使用上述结构体创建了一个队列,该队列的指针指向如下图所示:
在这里插入图片描述
(2)创建好链表队列结点结构体后,我们需要创建一个链表队列头方便我们对队列进行操作,创建代码如下:

TAILQ_HEAD(head, Node) head;

创建链表头使用的宏定义如下:

#define TAILQ_HEAD(name, type)                                  \
struct name {                                                           \
        type * tqh_first;           /* first element */             \
        type ** tqh_last;      /* addr of last next element */ \
}

宏定义展开后代码如下:

struct head { 
    Node * tqh_first; 
    Node ** tqh_last; 
} head;

该头也有两属性,第一个tqh_first指向队列的第一个元素,第二个属性也是一个二级指针,虽然命名为last,但是不是指向链表队列最后一个元素,而是指向链表队列最后一个元素的link.next属性,如下图所示:
在这里插入图片描述

(3)准备好队列头的结点结构体后,可以开始初始化一个链表队列的,初始化链表队列代码如下:

TAILQ_INIT(head);

队列初始化宏定义如下:

#define TAILQ_INIT(head) do {                                          \
        (head)->tqh_first = NULL;                                       \
        (head)->tqh_last = &(head)->tqh_first;                          \
} while (/*CONSTCOND*/0)

以上代码比较简单,就是对队列头的两个属性赋初始值。

(4)在链表队列中插入元素,既可以在链表队列的头部插入元素,也可以在链表队列的尾部插入元素,还可以在链表队列的中间插入元素(变异的队列。。。),接下来对插入元素的几种方式分别介绍。
(I)在链表队列尾部插入元素代码如下,动态分配一个Node结点node01,并将其插入到队列尾部

    Node * node01 = (Node *)malloc(sizeof(Node));
    TAILQ_INSERT_TAIL(head, node01, link);

所使用的TAILQ_INSERT_TAIL定义如下:

#define TAILQ_INSERT_TAIL(head, elm, field) do {                       \
        (elm)->field.tqe_next = NULL;                                   \
        (elm)->field.tqe_prev = (head)->tqh_last;                       \
        *(head)->tqh_last = (elm);                                      \
        (head)->tqh_last = &(elm)->field.tqe_next;                      \
} while (/*CONSTCOND*/0)

将在链表队列尾部插入元素的代码展开如下:

do { 
    (node01)->link.tqe_next = ((void *)0); 
	(node01)->link.tqe_prev = (head)->tqh_last; 
	*(head)->tqh_last = (node01); 
	(head)->tqh_last = &(node01)->link.tqe_next; 
} while ( 0)

插入元素宏定义展开后总共4行代码,下图图示了这4行代码实现流程:
假设队列中已经有3个结点,我们需要在尾部插入第4个结点
在这里插入图片描述
第1行代码将第四个结点的tqe_next设置为(void *)0,可以直接理解为置为NULL。
第2行代码中将tqe_pre指向前一个结点的tqe_next属性,因为head中的tqh_last指向了链表最后一个元素的tqe_next属性,所以这一步只需要将head->tqh_last的值直接赋给tqe_pre即可。
在这里插入图片描述
第3行代码实现将原队列最后一个元素tqe_next指向新增的node结点。
在这里插入图片描述
第4行代码就是将tqh_last指向新增结点的tqe_next属性。
在这里插入图片描述
通过上述4行代码我们便完成了在队列尾部新增结点的操作。通过上述操作我们可以发现,在链表尾部新增结点只需要4步,并不会因为链表节点数变多而导致时间复杂度增大,这就是head节点的意义,通过一个二级指针tql_last指向最后一个元素的tqe_next, 使得在队列尾部插入元素的时间复杂度为O(1)。
(II) 在链表队列头部插入元素所使用的宏如下,只要理解了上图中的指针指向,下面这个宏分析起来应该也容易,这边就不做进一步分析。

#define QTAILQ_INSERT_HEAD(head, elm, field) do {                       \
        if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)        \
                (head)->tqh_first->field.tqe_prev =                     \
                    &(elm)->field.tqe_next;                             \
        else                                                            \
                (head)->tqh_last = &(elm)->field.tqe_next;              \
        (head)->tqh_first = (elm);                                      \
        (elm)->field.tqe_prev = &(head)->tqh_first;                     \
} while (/*CONSTCOND*/0)

(III)在链表队列中间插入元素使用的宏如下:

#define TAILQ_INSERT_BEFORE(listelm, elm, field) do {                  \
        (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
        (elm)->field.tqe_next = (listelm);                              \
        *(listelm)->field.tqe_prev = (elm);                             \
        (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \
} while (/*CONSTCOND*/0)

如下图已经有3个结点了,我们需要将一个新的结点插入到倒数第二的位置
在这里插入图片描述
前2行代码将新增结点的tqe_next指向了原队列第2个结点的tqe_next属性,将新增结点的tqe_next属性指向被插入结点。
在这里插入图片描述
第3行代码将被插入位置的前一个结点的tqe_next指向新增结点,第4行代码将被插入结点的tqe_pre指向新增结点的tqe_next属性。
在这里插入图片描述
(5)遍历队列使用的宏如下:

#define TAILQ_FOREACH(var, head, field)                                \
        for ((var) = ((head)->tqh_first);                               \
                (var);                                                  \
                (var) = ((var)->field.tqe_next))

(6)删除队列元素的宏如下:

#define TAILQ_REMOVE(head, elm, field) do {                            \
        if (((elm)->field.tqe_next) != NULL)                            \
                (elm)->field.tqe_next->field.tqe_prev =                 \
                    (elm)->field.tqe_prev;                              \
        else                                                            \
                (head)->tqh_last = (elm)->field.tqe_prev;               \
        *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \
        (elm)->field.tqe_prev = NULL;                                   \
} while (/*CONSTCOND*/0)
  • 16
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值