libevent源碼分析之 tail queue

           最近開始學習libevent源碼, 發現其存儲各類event的隊列用的是OpenBSD: queue.h的帶有尾節點信息的隊列實現(帶有尾節點是顯然的, 這樣插入節點的時候不用從頭開始遍歷, 只要o(1)時間便可), 該實現用一系列宏實現的隊列數據結構的抽象和範型, 發現其實現的確實很精妙, 因而就深刻進去學習其實現, 以備在之後的項目中使用。數據結構

            tail queue涉及如下的數據結構:佈局

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

            看看libevent中是如何使用的:學習

TAILQ_HEAD (event_list, event);     //聲明瞭event_list這種數據結構
//擴展開就爲
struct event_list {        \
 struct event *tqh_first; /* first element */   \
 struct event **tqh_last; /* addr of last next element */  \
}

           很明顯咱們知道TAILQ_HEAD是用來聲明event_list這種數據結構, 而這個鏈表的節點類型爲struct event, tqh_first表示該鏈表的第一個節點, tqh_last表示最後一個節點的TAILQ_ENTRY字段中tqe_next值的地址(見下文)。指針

           再看看TAILQ_ENTRY:code

 struct event {
 。。。
 TAILQ_ENTRY (event) ev_next;
 TAILQ_ENTRY (event) ev_timeout_next;
 TAILQ_ENTRY (event) ev_add_next;
 。。。
 }
 //擴展開爲
 struct event {
 。。。
 struct { struct event *tqe_next;  struct event **tqe_prev; } ev_next;
 struct { struct event *tqe_next;  struct event **tqe_prev; } ev_timeout_next;
 struct { struct event *tqe_next;  struct event **tqe_prev; } ev_add_next;
 。。。
 }

           由此可知TAILQ_ENTRY是在鏈表的實際節點結構中儲存鏈表先後繼節點信息的數據類型。tqe_next表示下一個節點指針, tqe_prev表示前一個節點的TAILQ_ENTRY字段中tqe_next值的地址。隊列

           並且咱們發現:一、上面這兩種結構中他們的內存分配是同樣的 2 第二個字段tqe_prev、tqh_last都是個二級指針, 而不是前一個節點的指針或尾巴節點的指針(要是咱們實現一個待尾巴節點信息的鏈表, 估計咱們都會這樣實現, 二級指針可讓某些臨界條件下的操做不用特殊處理, 《c與指針》的鏈表實現那一章有很好的說明), tqe_prev表示前一個節點的TAILQ_ENTRY字段中tqe_next值的地址, 實際上也是TAILQ_ENTRY字段的地址。內存

          下面給出此鏈表的結構圖:element

          

         

           下面來看一下鏈表相關元素的訪問:源碼

#define TAILQ_FIRST(head)  ((head)->tqh_first)
#define TAILQ_END(head)   NULL
#define TAILQ_NEXT(elm, field)  ((elm)->field.tqe_next)
#define TAILQ_LAST(head, headname)     \
 (*(((struct headname *)((head)->tqh_last))->tqh_last))
/* XXX */
#define TAILQ_PREV(elm, headname, field)    \
 (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_EMPTY(head)      \
 (TAILQ_FIRST(head) == TAILQ_END(head))
#define TAILQ_FOREACH(var, head, field)     \
 for((var) = TAILQ_FIRST(head);     \
     (var) != TAILQ_END(head);     \
     (var) = TAILQ_NEXT(var, field))
#define TAILQ_FOREACH_REVERSE(var, head, field, headname) \
 for((var) = TAILQ_LAST(head, headname);     \
     (var) != TAILQ_END(head);       \
     (var) = TAILQ_PREV(var, headname, field))

         這段代碼其它部分都很好理解, 惟獨TAILQ_LAST(head, headname),  TAILQ_PREV(elm, headname, field) 有些複雜。就拿TAILQ_LAST(head, headname)分析, 觀察鏈表結構圖可知((head)->tqh_last))表示鏈表中最後一個節點的TAILQ_ENTRY字段中next結構的地址, 上文也講過TAILQ_ENTRY字段中next結構的地址實際上也是TAILQ_ENTRY結構的地址, 因爲(struct headname )與 TAILQ_ENTRY的內存佈局是同樣的, 因此(((struct headname *)((head)->tqh_last))獲得的是倒數第二個節點TAILQ_ENTRY字段的地址, 再經過此值就能夠獲取最後一個節點的指針。
    
         如下是鏈表的基本操做:
        
#define TAILQ_INIT(head) do {      \
 (head)->tqh_first = NULL;     \
 (head)->tqh_last = &(head)->tqh_first;    \
} while (0)
#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)
#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)
#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)
#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)
#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)
#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)
         參考鏈表結構圖, 很容易理解, 這裏就很少作說明了。
相關文章
相關標籤/搜索