链表之STAILQ

STAILQ 示意图

请添加图片描述

STAILQ 和 LIST 的不同是,head 里面有个指针 stqh_last,指向最后一个节点的 stqe_next

注意:有的地方也把 STAILQ 叫 simple queue,将接口前缀改为 SIMPLEQ_

接口和实现

先贴代码。(注意:不同版本的代码可能不同)

/*
 * Singly-linked Tail queue declarations.
 */
#define	STAILQ_HEAD(name, type)						\
struct name {								\
	struct type *stqh_first;/* first element */			\
	struct type **stqh_last;/* addr of last next element */		\
}

#define	STAILQ_HEAD_INITIALIZER(head)					\
	{ NULL, &(head).stqh_first }

#define	STAILQ_ENTRY(type)						\
struct {								\
	struct type *stqe_next;	/* next element */			\
}

/*
 * Singly-linked Tail queue functions.
 */
#define	STAILQ_EMPTY(head)	((head)->stqh_first == NULL)

#define	STAILQ_FIRST(head)	((head)->stqh_first)

#define	STAILQ_FOREACH(var, head, field)				\
	for((var) = STAILQ_FIRST((head));				\
	   (var);							\
	   (var) = STAILQ_NEXT((var), field))

#define	STAILQ_INIT(head) do {						\
	STAILQ_FIRST((head)) = NULL;					\
	(head)->stqh_last = &STAILQ_FIRST((head));			\
} while (0)

#define	STAILQ_INSERT_AFTER(head, tqelm, elm, field) do {		\
	if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
		(head)->stqh_last = &STAILQ_NEXT((elm), field);		\
	STAILQ_NEXT((tqelm), field) = (elm);				\
} while (0)

#define	STAILQ_INSERT_HEAD(head, elm, field) do {			\
	if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL)	\
		(head)->stqh_last = &STAILQ_NEXT((elm), field);		\
	STAILQ_FIRST((head)) = (elm);					\
} while (0)

#define	STAILQ_INSERT_TAIL(head, elm, field) do {			\
	STAILQ_NEXT((elm), field) = NULL;				\
	*(head)->stqh_last = (elm);					\
	(head)->stqh_last = &STAILQ_NEXT((elm), field);			\
} while (0)

#define	STAILQ_LAST(head, type, field)					\
	(STAILQ_EMPTY(head) ?						\
		NULL :							\
	        ((struct type *)					\
		((char *)((head)->stqh_last) - offsetof(struct type, field))))

#define	STAILQ_NEXT(elm, field)	((elm)->field.stqe_next)

#define	STAILQ_REMOVE(head, elm, type, field) do {			\
	if (STAILQ_FIRST((head)) == (elm)) {				\
		STAILQ_REMOVE_HEAD(head, field);			\
	}								\
	else {								\
		struct type *curelm = STAILQ_FIRST((head));		\
		while (STAILQ_NEXT(curelm, field) != (elm))		\
			curelm = STAILQ_NEXT(curelm, field);		\
		if ((STAILQ_NEXT(curelm, field) =			\
		     STAILQ_NEXT(STAILQ_NEXT(curelm, field), field)) == NULL)\
			(head)->stqh_last = &STAILQ_NEXT((curelm), field);\
	}								\
} while (0)

#define	STAILQ_REMOVE_HEAD(head, field) do {				\
	if ((STAILQ_FIRST((head)) =					\
	     STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL)		\
		(head)->stqh_last = &STAILQ_FIRST((head));		\
} while (0)

#define	STAILQ_REMOVE_HEAD_UNTIL(head, elm, field) do {			\
	if ((STAILQ_FIRST((head)) = STAILQ_NEXT((elm), field)) == NULL)	\
		(head)->stqh_last = &STAILQ_FIRST((head));		\
} while (0)

#define STAILQ_REMOVE_AFTER(head, elm, field) do {			\
	if ((STAILQ_NEXT(elm, field) =					\
	     STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL)	\
		(head)->stqh_last = &STAILQ_NEXT((elm), field);		\
} while (0)

举例

咱们看个例子,例子来自:https://manpages.courier-mta.org/htmlman3/stailq.3.html

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/queue.h>

struct entry {
    int data;
    STAILQ_ENTRY(entry) entries;        /* Singly linked tail queue */
};

STAILQ_HEAD(stailhead, entry);

int
main(void)
{
    struct entry *n1, *n2, *n3, *np;
    struct stailhead head;                  /* Singly linked tail queue
                                               head */

    STAILQ_INIT(&head);                     /* Initialize the queue */

    n1 = malloc(sizeof(struct entry));      /* Insert at the head */
    STAILQ_INSERT_HEAD(&head, n1, entries);

    n1 = malloc(sizeof(struct entry));      /* Insert at the tail */
    STAILQ_INSERT_TAIL(&head, n1, entries);

    n2 = malloc(sizeof(struct entry));      /* Insert after */
    STAILQ_INSERT_AFTER(&head, n1, n2, entries);

    STAILQ_REMOVE(&head, n2, entry, entries); /* Deletion */
    free(n2);

    n3 = STAILQ_FIRST(&head);
    STAILQ_REMOVE_HEAD(&head, entries);     /* Deletion from the head */
    free(n3);

    n1 = STAILQ_FIRST(&head);
    n1->data = 0;
    for (int i = 1; i < 5; i++) {
        n1 = malloc(sizeof(struct entry));
        STAILQ_INSERT_HEAD(&head, n1, entries);
        n1->data = i;
    }
                                            /* Forward traversal */
    STAILQ_FOREACH(np, &head, entries)
        printf("%i\n", np->data);
                                            /* TailQ deletion */
    n1 = STAILQ_FIRST(&head);
    while (n1 != NULL) {
        n2 = STAILQ_NEXT(n1, entries);
        free(n1);
        n1 = n2;
    }
    STAILQ_INIT(&head);

    exit(EXIT_SUCCESS);
}

代码分析

STAILQ_ENTRY 和 STAILQ_HEAD

struct entry {
    int data;
    STAILQ_ENTRY(entry) entries;        /* Singly linked tail queue */
};

STAILQ_HEAD(stailhead, entry);

宏替换后是

struct entry {
    int data;
    struct { 
        struct entry *stqe_next; 
    } entries;
};

struct stailhead { 
    struct entry *stqh_first; 
    struct entry **stqh_last; 
};

其实和 SLIST 差不多,唯一的不同是第 10 行,多了一个指向尾节点的指针(准确地说是指向尾节点的 stqe_next)

STAILQ_INIT 和 STAILQ_INSERT_HEAD

    struct entry *n1, *n2, *n3, *np;
    struct stailhead head;                  /* Singly linked tail queue
                                               head */

    STAILQ_INIT(&head);                     /* Initialize the queue */

    n1 = malloc(sizeof(struct entry));      /* Insert at the head */
    STAILQ_INSERT_HEAD(&head, n1, entries);

5:宏展开是

(&head)->stqh_first = ((void *)0); 
(&head)->stqh_last = &(&head)->stqh_first; 

链表的初始化为空,stqh_first 等于 NULL,stqh_last 指向自己的 stqh_first

8:宏展开是

if (((n1)->entries.stqe_next = (&head)->stqh_first) == ((void *)0)) 
	(&head)->stqh_last = &(n1)->entries.stqe_next; 
(&head)->stqh_first = (n1); 

把 n1 插入链表的第一个位置,会影响哪个指针的值呢?

  1. stqh_first
  2. n1 的 stqe_next
  3. 如果链表为空,那么插入 n1 后,stqh_last 也要改变

第 1 行解决了 2

第 2 行解决了 3

第 3 行解决了 1

STAILQ_INSERT_TAIL

    n1 = malloc(sizeof(struct entry));      /* Insert at the tail */
    STAILQ_INSERT_TAIL(&head, n1, entries);

2:宏替换是

    (n1)->entries.stqe_next = ((void *)0); 
    *(&head)->stqh_last = (n1); 
    (&head)->stqh_last = &(n1)->entries.stqe_next; 

把 n1 插入链表的最后一个位置,会影响哪些指针的值呢?

  1. stqh_last
  2. n1 的 stqe_next,需要为 NULL
  3. 原先的最后一个节点(假设是 n8)的 stqe_next

第 1 行解决了 2

第 2 行有点麻烦,(&head)->stqh_last 指向 n8 节点的 stqe_next,*(&head)->stqh_last 就是 n8 节点的 stqe_next,赋值 n1 给它,解决了 3

第 3 行解决了 1

STAILQ_INSERT_AFTER

    n2 = malloc(sizeof(struct entry));      /* Insert after */
    STAILQ_INSERT_AFTER(&head, n1, n2, entries);

第 2 行的意思是把 n2 插入到 n1 的后面

代码展开后是

if (((n2)->entries.stqe_next = (n1)->entries.stqe_next) == ((void *)0)) 
    (&head)->stqh_last = &(n2)->entries.stqe_next; 
(n1)->entries.stqe_next = (n2);

把 n2 插入到 n1 的后面,会影响哪些指针的值呢?

  1. n1 的 stqe_next
  2. n2 的 stqe_next
  3. 如果 n2 是最后一个节点,还要修改 (&head)->stqh_last

第 1 行的 (n2)->entries.stqe_next = (n1)->entries.stqe_next) 解决了 2

第 3 行解决了 1

第 2 行解决了 3

STAILQ_REMOVE

    STAILQ_REMOVE(&head, n2, entry, entries); /* Deletion */
    free(n2);

第 1 行展开是,有点长

if ((&head)->stqh_first == (n2)) { 
    if ((((&head))->stqh_first = ((&head))->stqh_first->entries.stqe_next) == ((void *)0)) 
        ((&head))->stqh_last = &((&head))->stqh_first; 
} else { 
    struct entry *curelm = (&head)->stqh_first; 
    while (curelm->entries.stqe_next != (n2)) 
        curelm = curelm->entries.stqe_next; 
    if ((curelm->entries.stqe_next = curelm->entries.stqe_next->entries.stqe_next) == ((void *)0)) 
        (&head)->stqh_last = &(curelm)->entries.stqe_next; 
} 

分 2 种情况。n2 是不是第一个节点(第 1 行代码),如果是,需要修改的指针有

  1. (&head)->stqh_first
  2. 如果 n2 是仅有的一个节点,那么还需要修改 (&head))->stqh_last

第 2 行的前半句解决了 1

第 3 行解决了 2

如果 n2 不是第一个节点,那就执行 5-10,先查找 n2 (6-7 行),当找到的时候, curelm 代表 n2 前面的那个节点。此时需要修改的指针有:

  1. curelm 的 stqe_next
  2. 如果 n2 是最后一个节点,删除它后需要修改 (&head))->stqh_last

第 8 行的前半句解决了 1

第 9 行解决了 2

STAILQ_FIRST 和 STAILQ_REMOVE_HEAD

    n3 = STAILQ_FIRST(&head);
    STAILQ_REMOVE_HEAD(&head, entries);     /* Deletion from the head */
    free(n3);

STAILQ_FIRST 比较简单,就是取链表的第一个节点,注意,不是删除

第 1 行展开:n3 = ((&head)->stqh_first);

STAILQ_REMOVE_HEAD 是删除第一个节点

第 2 行展开:

if (((&head)->stqh_first = (&head)->stqh_first->entries.stqe_next) == ((void *)0)) 
    (&head)->stqh_last = &(&head)->stqh_first; 

如果删除之前,仅有一个节点,那么需要修改 stqh_last,也就是第 2 行

如果有多个节点,那么只需要修改 stqh_first(第 1 行的前半句)

STAILQ_FOREACH

/* Forward traversal */
STAILQ_FOREACH(np, &head, entries)
	printf("%i\n", np->data);

STAILQ_FOREACH(np, &head, entries) 用来遍历链表的每个节点。第一个参数是临时变量,指向当前的节点,第二个参数是表头的地址,第三个 entries 是无标签结构体的成员名。

第 2 行展开:

 for ((np) = ((&head)->stqh_first); (np); (np) = ((np)->entries.stqe_next))
        printf("%i\n", np->data);

典型的遍历。注意,这种遍历是不能删除的,因为如果把 np 指向的节点删除了,

(np)->entries.stqe_next 这句就不对了。

STAILQ_NEXT

/* TailQ deletion */
    n1 = STAILQ_FIRST(&head);
    while (n1 != NULL) {
        n2 = STAILQ_NEXT(n1, entries);
        free(n1);
        n1 = n2;
    }

STAILQ_NEXT(n1, entries) 表示取 n1 的下一个节点

第 4 行展开是 n2 = ((n1)->entries.stqe_next); 比较简单。

这段代码也展示了如何删除整个链表。

还有几个宏,例子里面没有用到,我们也看一下。

STAILQ_EMPTY

#define	STAILQ_EMPTY(head)	((head)->stqh_first == NULL)

这个很好理解,不解释了。

STAILQ_LAST

#define	STAILQ_LAST(head, type, field)					\
	(STAILQ_EMPTY(head) ?						\
		NULL :							\
	        ((struct type *)					\
		((char *)((head)->stqh_last) - offsetof(struct type, field))))

注意,我的 Linux 操作系统上,并不包含这个宏,所以,如果要用,需要手工把这段代码包含进去。

这个宏的意思找到队列的最后一个节点。它是这么用的:

n1 = STAILQ_LAST(&head, entry, entries);

本文开始的时候,有

struct entry {
    int data;
    STAILQ_ENTRY(entry) entries;        /* Singly linked tail queue */
};

STAILQ_HEAD(stailhead, entry);

n1 = STAILQ_LAST(&head, entry, entries) 的后两个参数要对应第 3 行的定义。

我们回头看 STAILQ_LAST 的代码

#define	STAILQ_LAST(head, type, field)					\
	(STAILQ_EMPTY(head) ?						\
		NULL :							\
	        ((struct type *)					\
		((char *)((head)->stqh_last) - offsetof(struct type, field))))

如果链表为空,就返回 NULL,如果不为空呢?当然是顺着 (head)->stqh_last 找对最后一个节点。

问题是,(head)->stqh_last 指向的是最后一个节点的 entries.stqe_next 域

struct entry {
    int data;
    struct { 
        struct entry *stqe_next; 
    } entries;
};

也就是第 4 行,(head)->stqh_last 的值是 struct entry *stqe_next 的地址,而不是 struct entry 的地址,所以需要转换一下。转换需要用到

offsetof(struct type, field)

这也是一个宏,代码大概在 /usr/src/linux-headers-4.8.0-36-generic/include/linux/stddef.h

#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)

这里的 TYPE 表示某个结构体类型,MEMBER 表示结构体中的一个成员,这个宏求出了成员在结构体中的位置偏移(以字节为单位)

假设一个结构体变量(类型是 struct entry)的起始地址是 0x1234,它里面有个成员是 data,请问 data 变量的地址是多少?

可以这样求:

&((struct entry *)0x1234)->data

->比类型转换的优先级高,所以要加括号。((struct entry *)0x1234)->data 就得到了成员,再取地址,得到成员的地址,再来一个类型转换,转成无符号整形:

(size_t)&((struct entry *)0x1234)->data

终于得到成员的起始地址了,这时候我们看看它对于结构体变量的起始地址的偏移是多少?也就是用成员的地址减去结构体变量的地址:

(size_t)&((struct entry *)0x1234)->data - 0x1234

不难看出,这里的 0x1234 换成什么数字都可以,因为偏移是一个相对量,和起始地址无关

为了简单,那就把 0x1234 换成 0 吧。

我们做一般化的处理,把 struct entry 叫 TYPE,把 data 叫 MEMBER,所以就有了

((size_t)&((TYPE *)0)->MEMBER)

咱们回到正题上来,(head)->stqh_last 的值是 struct entry *stqe_next 的地址,而不是 struct entry 的地址,*如果我们知道 struct entry stqe_next 相对于 struct entry 的偏移 x,那么用 (head)->stqh_last 减去 x 就可以了。

利用 offsetof(struct type, field) 来求偏移,同时注意到 struct entry *stqe_next 和 entries 是同一个地址,所以偏移是:offsetof(struct entry , entries )

最后一个节点的地址是:

(struct entry *)((char*)((head)->stqh_last) - offsetof(struct entry, entries)))

把上面这个式子一般化,entry 换成 type,entries 换成 field,就得到了代码中的:

 ((struct type *)					\
		((char *)((head)->stqh_last) - offsetof(struct type, field))))

其实还有更简单的方法,直接利用内核里面的 container_of 宏,这里只做个提示,就不展开了。

【end】

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值