libuv系列--queue详解

一、目的

 

        在以前的博文《RT-Thread系列--双链表分析》我们介绍了如何实现双链表,其实这种双链表结构在linux内核中用得比较多,但是考虑到版权问题,libuv通过宏实现了同样功能的双链表结构。本篇的目的就是帮助大家理解其实现原理,涉及到的知识点包括:指针、指针数组、指针类型强转、左值

二、介绍

        涉及的源文件为libuv/src/queue.h

typedef void *QUEUE[2];

        上面的代码片段通过typedef定义了一个新类型QUEUE指针数组类型,数组大小为2个void类型的指针(也就是用来存放地址)。

        我们可以声明一个QUEUE类型的变量

void *p[2];
QUEUE q;  //相当于 void *q[2];

         也可以声明一个QUEUE类型的指针

void *p[2];
QUEUE *q = &p;

        QUEUE类型里面的两个void *指针的作用是用于存储链表中的前一个元素和后一个元素的地址(更准确的讲是存放QUEUE类型的指针地址值)

        在进一步介绍了QUEUE的相关接口细节之前我们先使用QUEUE构建一个双链表。

双链表

struct foo {
    QUEUE q;
    int val;
};

int main() {
    QUEUE header;
    QUEUE_INIT(&header);

    for (int i = 0; i < 10; i++) {
        struct foo *obj = calloc(1, sizeof(struct foo));
        obj->val = i;
        //QUEUE_INSERT_HEAD(&header, &obj->q);
        QUEUE_INSERT_TAIL(&header, &obj->q);
    }
    struct foo *obj;
    QUEUE *q;
    QUEUE_FOREACH(q, &header) {
        obj = QUEUE_DATA(q, struct foo, q);
        printf("obj value: %d\n", obj->val);
    }
    return 0;
}

        在构建一个新的双链表时,我们需要将QUEUE作为一个变量放在自定义的结构体中,比如上面的foo结构体中的变量q,通过q就可以将struct foo A/B/C连接起来。

        同时为了管理素双链表,我们还需要一个链表头,例如上面代码中的QUEUE header。

   


#define QUEUE_NEXT(q)       (*(QUEUE **) &((*(q))[0]))
#define QUEUE_PREV(q)       (*(QUEUE **) &((*(q))[1]))

         上面的两个宏QUEUE_NEXT(q)是为了获取双链表q元素下一个元素的地址,QUEUE_PREV(q)是获取上一个元素的地址;注意此处的q是QUEUE *类型的

        从上面的例子里面,我们可以看到foo A的q数组的第0个元素指向的是foo B的q,也就是说foo A的q数组里面第0个元素存放的地址值是foo B的q的地址 

        之所以这样复杂是为了保持类型一致成为左值(左值才可以被赋值),我们下面好好解释一下:

        (*(q))是对QUEUE *类型的q解引用

        ((*(q))[0])是获取第一个元素的值(值是一个地址,更准确的讲应该是QUEUE类型的指针值

        因为在类型Queue中[0]存放的是下一个元素的Queue类型的地址,所以我们需要对其进行类型强转(原本类型为void *)

        QUEUE * ((*(q))[0])

        但是这样类型强转之后是一个右值,无法对其赋值;如果只是进行取值操作是可以这样定义的。

        举例说明

//①
int a = 10;
//②
(char)a = 2;  //错误
//③
*((char *)&a) = 2; //正确

        在步骤②中我们是无法修改a的值等于2,但是③的方法却可以实现。 

        基于这样的原因,我们对

((*(q))[0])

        取地址,取地址后类型变成void **类型;然后我们再进行QUEUE **类型强制转换;然后再对其解引用(*);

        最终 QUEUE_NEXT(q)宏定义我们既可以用于取值操作,也可以用于赋值操作。

        

#define QUEUE_PREV_NEXT(q)  (QUEUE_NEXT(QUEUE_PREV(q)))
#define QUEUE_NEXT_PREV(q)  (QUEUE_PREV(QUEUE_NEXT(q)))

         链表初始化

#define QUEUE_INIT(q)                                                         \
  do {                                                                        \
    QUEUE_NEXT(q) = (q);                                                      \
    QUEUE_PREV(q) = (q);                                                      \
  }                                                                           \
  while (0)

        将q的next/prev都指向自身,此时q是一个空链表,例如QUEUE_INIT(header)将header进行初始化。

         

         判断链表是否为空

#define QUEUE_EMPTY(q)                                                        \
  ((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q))

        获取链表的第一个元素(必须是非空链表)

#define QUEUE_HEAD(q)                                                         \
  (QUEUE_NEXT(q))

        通过节点内QUEUE变量的地址获取的元素的首地址

/* Public macros. */
#define QUEUE_DATA(ptr, type, field)                                          \
  ((type *) ((char *) (ptr) - offsetof(type, field)))

        在头部插入新元素

#define QUEUE_INSERT_HEAD(h, q)                                               \
  do {                                                                        \
    QUEUE_NEXT(q) = QUEUE_NEXT(h);                                            \
    QUEUE_PREV(q) = (h);                                                      \
    QUEUE_NEXT_PREV(q) = (q);                                                 \
    QUEUE_NEXT(h) = (q);                                                      \
  }                                                                           \
  while (0)

        在 尾部插入新元素

#define QUEUE_INSERT_TAIL(h, q)                                               \
  do {                                                                        \
    QUEUE_NEXT(q) = (h);                                                      \
    QUEUE_PREV(q) = QUEUE_PREV(h);                                            \
    QUEUE_PREV_NEXT(q) = (q);                                                 \
    QUEUE_PREV(h) = (q);                                                      \
  }                                                                           \
  while (0)

        移除节点

#define QUEUE_REMOVE(q)                                                       \
  do {                                                                        \
    QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q);                                       \
    QUEUE_NEXT_PREV(q) = QUEUE_PREV(q);                                       \
  }                                                                           \
  while (0)

  

        遍历链表

/* Important note: mutating the list while QUEUE_FOREACH is
 * iterating over its elements results in undefined behavior.
 */
#define QUEUE_FOREACH(q, h)                                                   \
  for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q))

        上面的遍历过程中不能新增或者删除节点

        链接拼接


#define QUEUE_ADD(h, n)                                                       \
  do {                                                                        \
    QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n);                                       \
    QUEUE_NEXT_PREV(n) = QUEUE_PREV(h);                                       \
    QUEUE_PREV(h) = QUEUE_PREV(n);                                            \
    QUEUE_PREV_NEXT(h) = (h);                                                 \
  }                                                                           \
  while (0)

        将n链表中的元素拼接到h链表的尾部

        分割链表

#define QUEUE_SPLIT(h, q, n)                                                  \
  do {                                                                        \
    QUEUE_PREV(n) = QUEUE_PREV(h);                                            \
    QUEUE_PREV_NEXT(n) = (n);                                                 \
    QUEUE_NEXT(n) = (q);                                                      \
    QUEUE_PREV(h) = QUEUE_PREV(q);                                            \
    QUEUE_PREV_NEXT(h) = (h);                                                 \
    QUEUE_PREV(q) = (n);                                                      \
  }                                                                           \
  while (0)

        将h链表从q节点开始分割到链表n中 

        移动链表

#define QUEUE_MOVE(h, n)                                                      \
  do {                                                                        \
    if (QUEUE_EMPTY(h))                                                       \
      QUEUE_INIT(n);                                                          \
    else {                                                                    \
      QUEUE* q = QUEUE_HEAD(h);                                               \
      QUEUE_SPLIT(h, q, n);                                                   \
    }                                                                         \
  }                                                                           \
  while (0)

        将链表中的节点移动到链表n中 

三、实战

#include "queue.h"

#include <stdio.h>
#include <stdlib.h>

struct foo {
    QUEUE q;
    int val;
};

int main() {
    QUEUE header;
    QUEUE_INIT(&header);

    for (int i = 0; i < 10; i++) {
        struct foo *obj = calloc(1, sizeof(struct foo));
        obj->val = i;
        //QUEUE_INSERT_HEAD(&header, &obj->q);
        QUEUE_INSERT_TAIL(&header, &obj->q);
    }
    struct foo *obj;
    QUEUE *q;
    QUEUE_FOREACH(q, &header) {
        obj = QUEUE_DATA(q, struct foo, q);
        printf("obj value: %d\n", obj->val);
    }

    QUEUE header2;

    QUEUE_MOVE(&header, &header2);
    while (!QUEUE_EMPTY(header2)) {
        q = QUEUE_HEAD(&header2);
        QUEUE_REMOVE(q);
        obj = QUEUE_DATA(q, struct foo, q);
        printf("remove: %d, add tail\n", obj->val);
        QUEUE_INSERT_TAIL(&header, q);
    }

    QUEUE_MOVE(&header, &header2);
    while (!QUEUE_EMPTY(header2)) {
        q = QUEUE_HEAD(&header2);
        QUEUE_REMOVE(q);
        //安全遍历
        obj = QUEUE_DATA(q, struct foo, q);
        printf("free: %d\n", obj->val);
        free(q);
    }

    return 0;
}

        以上,就是libuv queue的全部内容。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值