sys/queue使用说明与TAILQ使用举例

每个写C的同学想必都会羡慕C++方便的STL,但是无奈标准C是没有这些库的,毕竟C语言不支持泛型,不过好在大多数Linux下都可用sys/queue,源自BSD:

https://unix.superglobalmegacorp.com/xnu/newsrc/bsd/sys/queue.h.html

这个头文件用宏实现了很多基础数据结构,如:单链表、双链表、队列等。用起来也比较方便,只需要引入一个头文件即可。如著名的的libevent项目就大量使用了这个头文件里的TAILQ. 不过C的宏实现的库并没有C++那么直白好用,还是需要看懂实现才行。

TAILQ使用举例

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

#include <sys/queue.h>

struct node {
    char c;
    TAILQ_ENTRY(node) next; // define queue node
};

TAILQ_HEAD(head_s, node); // define queue head

int main()
{
    struct head_s queue;
    TAILQ_INIT(&queue); // init queue

    const char *p = "que";
    while (*p) {
        struct node *e = malloc(sizeof(struct node));
        e->c = *p;
        TAILQ_INSERT_TAIL(&queue, e, next);  // push element into queue tail
        printf("push %p %c\n", e, e->c);
        p++;
    }

    while (!TAILQ_EMPTY(&queue)) {
        struct node *first = TAILQ_FIRST(&queue);  // get queue head element
        printf("pop  %p %c\n", first, first->c);
        TAILQ_REMOVE(&queue, first, next);  // remove queue head element
        free(first);
    }
    printf("OK\n");
    return 0;
}

对于C的这些宏API,要通过看源码来理解到底传入的参数是干什么,传入的是类型名字,还是结构体字段名字,还是变量。毕竟,宏仅仅是完成字符串的替换而已。

理解TAILQ宏实现

理解上面代码,可以先看源码的实现,也可用gcc -E来看下展开后的宏的样子:

struct node {
    char c;
    struct {
        struct node *tqe_next;
        struct node **tqe_prev;
    } next;
};

struct head_s {
    struct node *tqh_first;
    struct node **tqh_last;
};

int main()
{
    struct head_s queue;
    do {
        (((&queue))->tqh_first) = ((void *) 0);
        (&queue)->tqh_last = &(((&queue))->tqh_first);;
    } while (0);

    const char *p = "que";
    while (*p) {
        struct node *e = malloc(sizeof(struct node));
        e->c = *p;
        do {
            (((e))->next.tqe_next) = ((void *) 0);
            (e)->next.tqe_prev = (&queue)->tqh_last;
            *(&queue)->tqh_last = (e);
            (&queue)->tqh_last = &(((e))->next.tqe_next);;;
        } while (0);
        printf("push %p %c\n", e, e->c);
        p++;
    }

    while (!((&queue)->tqh_first == ((void *) 0))) {
        struct node *first = ((&queue)->tqh_first);
        printf("pop  %p %c\n", first, first->c);
        do { ;;
            if (((((first))->next.tqe_next)) != ((void *) 0))
                (((first))->next.tqe_next)->next.tqe_prev = (first)->next.tqe_prev;
            else
                { (&queue)->tqh_last = (first)->next.tqe_prev;; }
            *(first)->next.tqe_prev = (((first))->next.tqe_next);;;;
        } while (0);
        free(first);
    }
    printf("OK\n");
    return 0;
}

可以看出,TAILQ的底层结构是双链表,prev前驱指针是一个二级指针,这个可以在判断是否头结点的指向自己的内存即可,不用特殊处理为NULL,比较巧妙,省掉了头结点的特殊处理。类似比较经典的二级指针使用还有:Linus:利用二级指针删除单向链表

TAILQ使用注意事项

使用这些宏API的时候,要注意插入的数据节点的生命周期,注意对应节点是存储在栈区还是堆区。比如:局部变量node插入队列后,如果在调用者访问里面节点就不对了,因为对应的内存已经释放掉了。

一般情况,用C来开发复杂系统时,先考虑Data和Control分离,存储结构式什么样子的,对应的控制字段如何维护等,还要考虑对应对象的声明周期等。

参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值