链表之STAILQ
文章目錄
- STAILQ 示意圖
- 接口和實現
- 舉例
- 代碼分析
- STAILQ_ENTRY 和 STAILQ_HEAD
- STAILQ_INIT 和 STAILQ_INSERT_HEAD
- STAILQ_INSERT_TAIL
- STAILQ_INSERT_AFTER
- STAILQ_REMOVE
- STAILQ_FIRST 和 STAILQ_REMOVE_HEAD
- STAILQ_FOREACH
- STAILQ_NEXT
- STAILQ_EMPTY
- STAILQ_LAST
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 queuehead */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 queuehead */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 行解決了 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 行解決了 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 行的 (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 行代碼),如果是,需要修改的指針有
第 2 行的前半句解決了 1
第 3 行解決了 2
如果 n2 不是第一個節點,那就執行 5-10,先查找 n2 (6-7 行),當找到的時候, curelm 代表 n2 前面的那個節點。此時需要修改的指針有:
第 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】
總結