C- libevent TAILQ 源码剖析

在libevent中有一个基础结构TAILQ到处都可看到,TAILQ是由一系列宏定义的,这种数据结构的设计以及工程技巧是非常值得学习的,结合我自己的理解,深入的分析TAILQ。建议开始阅读文本前,在一个不易被打扰的环境,且有一份笔和纸,自己画一画节点间的连接关系图。

为了方便表述,有几个自定义名词如下:

头指针节点:指的是TAILQ的管理节点,而不是链表中的元素节点

元素节点:即链表中的节点,本文中也会用entry表示

头节点:即链表中的第一个元素节点

尾节点:即链表中的最后一个元素节点

TAILQ_HEAD

TAILQ有一个头指针节点来"管理"整个链表

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

tqh_first指向 链表的头节点,tqh_last指向 链表的尾节点。一般在链表这种数据结构中,使用头指针节点来管理链表是更好的方式,一来可以充分指针的灵活性,二来可以占用更少的内存空间。

TAILQ_ENTRY

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

链表中的元素节点的结构,可以发现都是指针,并没看到实际存储节点数据的成员,这里也是一个设计技巧,目的为了提高entry的灵活性。为了更加形象的理解,来一张生活气息的图:

在这里插入图片描述

可以将TAILQ_ENTRY的结构体看作是一个个钩子(指针),搭好这个架子(链表框架),至于每个钩子上挂什么,就看使用者自己分配,是想挂鸡呢还是挂鸭都可以。

TAILQ_INIT

// 初始化头指针节点
#define TAILQ_INIT(head) do { \
    (head)->tqh_first = NULL; \
    (head)->tqh_last = &(head)->tqh_first; \
} while (0)

TAILQ_INIT初始化链表头指针节点,该头指针节点由两个成员组成:*tqh_first,**tqh_last,头指针节点的作用是将整个链表首尾节点连接起来形成闭环。

从初始化可以看出,将hqh_first指向NULL,tqh_last指向tqh_first;

TAILQ_INSERT_TAIL

尾插

// 将新节点(elm)尾插入队列
#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 (0)

(elm)->field.tqe_next = NULL;	
// 将新节点的tqe_next指向NULL
(elm)->field.tqe_prev = (head)->tqh_last; 
// 将新节点的tqe_prev指向tqh_last,因为tqh_last的值是 旧尾节点的tqe_next的地址,所以新节点的tqe_prev是指向 旧尾节点的tqe_next,这样就将新节点和旧尾节点关联起来(从后往前)。
// 如果是一个没有元素节点的空链表,新节点的tqe_pre是指向"头指针节点"的tqh_first
*(head)->tqh_last = (elm);
// *tqh_last 指向新节点的地址,而*tqh_last为旧尾节点的tqe_next,也就是将旧尾节点的tqe_next指向 新的尾节点,如此便将新尾节点和旧尾节点关联起来(从前往后)。
(head)->tqh_last = &(elm)->field.tqe_next;
// "头指针节点"的tqh_last指向 新节点的tqe_next
// 至此,新节点就尾插进队列

从文字上看,可能还是不太理解,你可以自己画一画节点之间的连接关系,也可以看看下面这个图:

在这里插入图片描述

TAILQ_REMOVE

// 将节点(elm)移出队列
#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;           \
} while (0)


if (((elm)->field.tqe_next) != NULL)// elm不是尾节点
	(elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev;
	//将elm的后一个节点的tqe_prev 指向 elm的前一个节点的tqe_next;即将elm的前一个节点和后一个节点关联起来
else //elm 是尾节点
	(head)->tqh_last = (elm)->field.tqe_prev;
	// tqh_last指向elm的前一个节点;即断开elm节点和tqh_last的关联,同时将elm的前一个节点作为新的尾节点
*((elm)->field.tqe_prev) = (elm)->field.tqe_next;// 将elm的前一个节点的tqe_next指向elm的后一个节点

说明:移除只是将 待移除节点 与前后节点的连接断开,若要释放该节点的内存还需要free。

看到这里,我们了解了TAILQ的 初始化、尾插新节点、删除指定节点。可能对于有些同学来说,以上内容还是有点儿抽象,所以接下来,我会给出简单的示例代码来帮助理解。当然,如果以上内容你能完全理解,就可以跳过该示例代码。

示例

link.h

struct link_entry {
	TAILQ_ENTRY(link_entry) next;
	char * user_data; //可以挂鸡或鸭
};

struct link {
	TAILQ_HEAD(link_q, link_entry) entries;
    int link_size;
    // something else
};

struct link* link_new(void);
void link_free(struct link* link);
int link_add(struct link* link, char* user_data);
void link_print(struct link* link);

在这里插入图片描述

link.c

struct link* link_new(void)
{
    struct link* link = malloc(sizeof(struct link));
    if (NULL == link)
        return NULL;

    TAILQ_INIT(&link->entries);
    link->link_size = 0;

    return link;
}

TAILQ_INIT展开后,如下图所示:

在这里插入图片描述

int link_add(struct link* link, char* user_data)
{
    struct link_entry* entry = malloc(sizeof(struct link_entry));
    if (NULL == entry)
        return -1;

    TAILQ_INSERT_TAIL(&link->entries, entry, next);
    entry->user_data = user_data;

    return 0;
}

TAILQ_INSERT_TAIL展开后,如下图所示:

在这里插入图片描述

void link_free(struct link* link)
{
    struct link_entry *entry;

    while ((entry = TAILQ_FIRST(&link->entries)) != NULL)
    {
        TAILQ_REMOVE(&link->entries, entry, next);
        link_entry_free(entry);
    }
    free(link);
}

TAILQ_REMOVE展开后,如下图所示:

在这里插入图片描述

void link_print(struct link* link)
{
    struct link_entry* entry = NULL;
    TAILQ_FOREACH(entry, &link->entries, next)
    {
        printf("user data:%s\n", entry->user_data); 
    }
}

main.c

int main(int argc, char** argv)
{
    char* user_data1 = (char*)malloc(16);
    char* user_data2 = (char*)malloc(16);

    strcpy(user_data1, "duck");
    strcpy(user_data2, "chicken");

    struct link* link = link_new();
    
    link_add(link, user_data1);
    link_add(link, user_data2);

    link_print(link);

    link_free(link);

    free(user_data1);
    free(user_data2);

    return 0;
}
// 打印输出
user data:duck
user data:chicken

TAILQ_INSERT_AFTER

后插

因为后插可能会涉及到尾节点,那么头指针节点的tqh_last有可能要被修改,所以需要head

// 将新节点(elm)插入到listelm节点后
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {      \
    if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
        (elm)->field.tqe_next->field.tqe_prev =         \
            &(elm)->field.tqe_next;             \
    else                                \
        (head)->tqh_last = &(elm)->field.tqe_next;      \
    (listelm)->field.tqe_next = (elm);              \
    (elm)->field.tqe_prev = &(listelm)->field.tqe_next;     \
} while (0)


do {
    if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL) // listelm不是尾节点,使elm新节点的tqe_next指向listelm节点的下一个节点
       (elm)->field.tqe_next->field.tqe_prev = &(elm)->field.tqe_next; // 使listelm的下一个节点的tqe_prev指向elm新节点的tqe_next
    else
        (head)->tqh_last = &(elm)->field.tqe_next; // listelm是尾节点,使"头指针节点"的tqh_last指向新节点的tqe_next,即elm变成尾节点
    (listelm)->field.tqe_next = (elm);// listelm的tqe_next指向新节点elm
    (elm)->field.tqe_prev = &(listelm)->field.tqe_next;// 新节点elm的tqe_prev指向listelm的tqe_next
} while (0)

TAILQ_INSERT_BEFORE

前插

// 将新节点(elm)插入到listelm节点前
#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 (0)


do {
    (elm)->field.tqe_prev = (listelm)->field.tqe_prev;// 使elm的tqe_prev指向listelm的前一个节点
    (elm)->field.tqe_next = (listelm);// 使elm的tqe_next指向listelm节点
    *(listelm)->field.tqe_prev = (elm);// 使listelm的前一个节点的tqe_next指向elm节点
    (listelm)->field.tqe_prev = &(elm)->field.tqe_next;// 使listelm的tqe_next指向elm的tqe_next的地址
} while (0)

TAILQ_INSERT_HEAD

头插

// 将新节点(elm)插入为头节点
#define TAILQ_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 (0)



do {
    if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)// 链表不为空,新节点elm的tqe_next指向链表的当前头节点
        (head)->tqh_first->field.tqe_prev = &(elm)->field.tqe_next;// 将链表当前头节点的tqe_prev指向新节点的tqe_next
    else
        (head)->tqh_last = &(elm)->field.tqe_next;// 链表为空,"头指针节点"的tqh_last指向新节点elm的tqe_next的地址
    (head)->tqh_first = (elm);// 头指针节点的tqh_first指向新节点 
    (elm)->field.tqe_prev = &(head)->tqh_first;// 新节点的tqe_prev指向"头指针节点"的tqh_first的地址
} while (0)

TAILQ_REPLACE

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


do {
    if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)// elm不是尾节点,使elm2的tqe_next指向elm的后一个节点
        (elm2)->field.tqe_next->field.tqe_prev = &(elm2)->field.tqe_next;// 使elm的后一个节点的tqe_prev指向elm2的tqe_next的地址
    else
        (head)->tqh_last = &(elm2)->field.tqe_next;// elm是尾节点,使thq_last指向elm2的tqe_next的地址
    (elm2)->field.tqe_prev = (elm)->field.tqe_prev;// elm2的tqe_prev指向elm的tqe_prev,即elm前一个节点的tqe_next的地址
    *(elm2)->field.tqe_prev = (elm2);// elm的前一个节点的tqe_next指向elm2
} while (0)

TAILQ_FIRST

// 获取头节点的地址
#define TAILQ_FIRST(head)       ((head)->tqh_first)

TAILQ_END

#define TAILQ_END(head)         NULL

TAILQ_NEXT

// 获取当前节点(elm)的下一个节点的地址
#define TAILQ_NEXT(elm, field)      ((elm)->field.tqe_next)

TAILQ_LAST

//获取尾节点地址
#define TAILQ_LAST(head, headname)                  \
    (*(((struct headname *)((head)->tqh_last))->tqh_last))
struct {              
    struct type *tqh_first;
    struct type **tqh_last;
}
// ((head)->tqh_last)是一个二级指针,它的值是尾节点tqe_next的地址
// 无论是一级指针还是二级指针,指针变量的值都是一个地址,只不过二级指针变量的值只能是一级指针变量的地址,而一级指针变量的值可以是普通变量的地址
// (struct headname *)((head)->tqh_last) 将tqh_last强转成struct headname的一级指针,但tqh_last的值不变,假定p = (struct headname *)((head)->tqh_last)(为了方便后面表述),此时p指向tqe_next所在内存的地址。

已知entry的结构体为
struct {
    struct type *tqe_next;
    struct type **tqe_prev;
}
// 已知tqe_next后一个成员是tqe_prev,又因为p的值等于尾节点tqe_next的地址,所以p->tqh_last的值等于尾节点tqe_prev的值,即尾节点的前一个节点的tqe_next的地址;
// 那么*(p->tqh_last) 就是 尾节点的前一个节点的tqe_next的值,也就得到了尾节点的首地址。

我认为这个宏用的非常巧妙,只有充分理解了结构体的存储和指针才能有此妙想和使用。

本质上就是对内存地址进行强制类型转换,然后利用指针和结构体的存储特性来获取特定的值。下面的示例,对这个原理做了一个简单的抽象,方便理解:

示例

在这里插入图片描述

TAILQ_PREV

// 获取当前节点的前一个节点
#define TAILQ_PREV(elm, headname, field)                \
    (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
// 理解了前面TAILQ_LAST,那么TAILQ_PREV也是同样的原理
// ((elm)->field.tqe_prev) ---> 前一个节点的tqe_next的地址
// ((struct headname *)((elm)->field.tqe_prev))->tqh_last ---> 前一个节点的tqe_prev,而前一个节点的tqe_prev的值是前两个节点的tqe_next的地址
// (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) ---> 前两个节点的tqe_next的值即为前一个节点的地址

TAILQ_EMPTY

#define TAILQ_EMPTY(head)                       \
    (TAILQ_FIRST(head) == TAILQ_END(head))

TAILQ_FOREACH

// 正向遍历
#define TAILQ_FOREACH(var, head, field)                 \
    for((var) = TAILQ_FIRST(head);                  \
        (var) != TAILQ_END(head);                   \
        (var) = TAILQ_NEXT(var, field))

TAILQ_FOREACH_REVERSE

// 逆向遍历
#define TAILQ_FOREACH_REVERSE(var, head, headname, field)       \
    for((var) = TAILQ_LAST(head, headname);             \
        (var) != TAILQ_END(head);                   \
        (var) = TAILQ_PREV(var, headname, field))

QQ讨论群:679603305
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值