轉載出處: http://blog.csdn.net/luotuo44/article/details/38374009
Libevent源碼中有一個queue.h文件,位於compat/sys目錄下。該文件裏面定義了5個數據結構,其中TAILQ_QUEUE是使得最廣泛的。本文就說一下這個數據結構。
隊列結構體:
TAILQ_QUEUE由下面兩個結構體一起配合工作。
-
#define TAILQ_HEAD(name, type) \
-
struct name { \
-
struct type *tqh_first; \
-
struct type **tqh_last; \
-
}
-
-
-
-
-
#define TAILQ_ENTRY(type) \
-
struct { \
-
struct type *tqe_next; \
-
struct type **tqe_prev; \
-
}
由這兩個結構體配合構造出來的隊列一般如下圖所示:
圖中,一級指針指向的是queue_entry_t這個結構體,即存儲queue_entry_t這個結構體的地址值。二級指針存儲的是一級地址變量的地址值。所以二級指針指向的是圖中的一級指針,而非結構體。圖中的1,2, 3爲隊列元素保存的一些值。
隊列操作宏函數以及使用例子:
除了這兩個結構體,在queue.h文件中,還爲TAILQ_QUEUE定義了一系列的訪問和操作函數。很不幸,它們是一些宏定義。這裏就簡單貼幾個函數(準確來說,不是函數)的代碼。
-
#define TAILQ_FIRST(head) ((head)->tqh_first)
-
-
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
-
-
#define TAILQ_INIT(head) do { \
-
(head)->tqh_first = NULL; \
-
(head)->tqh_last = &(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_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)
這些宏是很難看的,也沒必要直接去看這些宏。下面來看一個使用例子。有例子更容易理解。
-
-
-
struct queue_entry_t
-
{
-
int value;
-
-
-
TAILQ_ENTRY(queue_entry_t)entry;
-
};
-
-
-
-
TAILQ_HEAD(queue_head_t, queue_entry_t);
-
-
int main(int argc, char **argv)
-
{
-
struct queue_head_t queue_head;
-
struct queue_entry_t *q, *p, *s, *new_item;
-
int i;
-
-
TAILQ_INIT(&queue_head);
-
-
for(i = 0; i < 3; ++i)
-
{
-
p = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
p->value = i;
-
-
-
TAILQ_INSERT_TAIL(&queue_head, p, entry);
-
}
-
-
q = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
q->value = 10;
-
TAILQ_INSERT_HEAD(&queue_head, q, entry);
-
-
-
-
s = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
s->value = 20;
-
-
TAILQ_INSERT_AFTER(&queue_head, q, s, entry);
-
-
-
s = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
s->value = 30;
-
-
TAILQ_INSERT_BEFORE(p, s, entry);
-
-
-
-
s = TAILQ_FIRST(&queue_head);
-
printf("the first entry is %d\n", s->value);
-
-
-
s = TAILQ_NEXT(s, entry);
-
printf("the second entry is %d\n\n", s->value);
-
-
-
TAILQ_REMOVE(&queue_head, s, entry);
-
free(s);
-
-
-
new_item = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
new_item->value = 100;
-
-
s = TAILQ_FIRST(&queue_head);
-
-
TAILQ_REPLACE(&queue_head, s, new_item, entry);
-
-
-
printf("now, print again\n");
-
i = 0;
-
TAILQ_FOREACH(p, &queue_head, entry)
-
{
-
printf("the %dth entry is %d\n", i, p->value);
-
}
-
-
p = TAILQ_LAST(&queue_head, queue_head_t);
-
printf("last is %d\n", p->value);
-
-
p = TAILQ_PREV(p, queue_head_t, entry);
-
printf("the entry before last is %d\n", p->value);
-
}
例子並不難看懂。這裏就不多講了。
展開宏函數:
下面把這些宏翻譯一下(即展開),顯示出它們的本來面貌。這當然不是用人工方式去翻譯。而是用gcc 的-E選項。
閱讀代碼時要注意,tqe_prev和tqh_last都是二級指針,行爲會有點難理解。平常我們接觸到的雙向鏈表,next和prev成員都是一級指針。對於像鏈表A->B->C(把它們想象成雙向鏈表),通常B的prev指向A這個結構體本身。此時,B->prev->next指向了本身。但隊列Libevent的TAILQ_QUEUE,B的prev是一個二級指向,它指向的是A結構體的next成員。此時,*B->prev就指向了本身。當然,這並不能說用二級指針就方便。我覺得用二級指針理解起來更難,編寫代碼更容易出錯。
-
-
-
struct queue_entry_t
-
{
-
int value;
-
-
struct
-
{
-
struct queue_entry_t *tqe_next;
-
struct queue_entry_t **tqe_prev;
-
}entry;
-
};
-
-
-
struct queue_head_t
-
{
-
struct queue_entry_t *tqh_first;
-
struct queue_entry_t **tqh_last;
-
};
-
-
int main(int argc, char **argv)
-
{
-
struct queue_head_t queue_head;
-
struct queue_entry_t *q, *p, *s, *new_item;
-
int i;
-
-
-
do
-
{
-
(&queue_head)->tqh_first = 0;
-
-
(&queue_head)->tqh_last = &(&queue_head)->tqh_first;
-
}while(0);
-
-
for(i = 0; i < 3; ++i)
-
{
-
p = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
p->value = i;
-
-
-
do
-
{
-
(p)->entry.tqe_next = 0;
-
-
-
(p)->entry.tqe_prev = (&queue_head)->tqh_last;
-
-
-
-
-
-
*(&queue_head)->tqh_last = (p);
-
-
-
(&queue_head)->tqh_last = &(p)->entry.tqe_next;
-
}while(0);
-
}
-
-
q = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
q->value = 10;
-
-
-
do {
-
-
if(((q)->entry.tqe_next = (&queue_head)->tqh_first) != 0)
-
(&queue_head)->tqh_first->entry.tqe_prev = &(q)->entry.tqe_next;
-
else
-
(&queue_head)->tqh_last = &(q)->entry.tqe_next;
-
-
-
(&queue_head)->tqh_first = (q);
-
(q)->entry.tqe_prev = &(&queue_head)->tqh_first;
-
}while(0);
-
-
-
-
s = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
s->value = 20;
-
-
-
do
-
{
-
-
if (((s)->entry.tqe_next = (q)->entry.tqe_next) != 0)
-
(s)->entry.tqe_next->entry.tqe_prev = &(s)->entry.tqe_next;
-
else
-
(&queue_head)->tqh_last = &(s)->entry.tqe_next;
-
-
(q)->entry.tqe_next = (s);
-
(s)->entry.tqe_prev = &(q)->entry.tqe_next;
-
}while(0);
-
-
-
-
s = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
s->value = 30;
-
-
-
do
-
{
-
-
-
(s)->entry.tqe_prev = (p)->entry.tqe_prev;
-
(s)->entry.tqe_next = (p);
-
*(p)->entry.tqe_prev = (s);
-
(p)->entry.tqe_prev = &(s)->entry.tqe_next;
-
}while(0);
-
-
-
-
-
-
s = ((&queue_head)->tqh_first);
-
printf("the first entry is %d\n", s->value);
-
-
-
s = ((s)->entry.tqe_next);
-
printf("the second entry is %d\n\n", s->value);
-
-
-
-
do
-
{
-
if (((s)->entry.tqe_next) != 0)
-
(s)->entry.tqe_next->entry.tqe_prev = (s)->entry.tqe_prev;
-
else (&queue_head)->tqh_last = (s)->entry.tqe_prev;
-
-
*(s)->entry.tqe_prev = (s)->entry.tqe_next;
-
}while(0);
-
-
free(s);
-
-
-
new_item = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));
-
new_item->value = 100;
-
-
-
s = ((&queue_head)->tqh_first);
-
-
-
-
do
-
{
-
if (((new_item)->entry.tqe_next = (s)->entry.tqe_next) != 0)
-
(new_item)->entry.tqe_next->entry.tqe_prev = &(new_item)->entry.tqe_next;
-
else
-
(&queue_head)->tqh_last = &(new_item)->entry.tqe_next;
-
-
(new_item)->entry.tqe_prev = (s)->entry.tqe_prev;
-
*(new_item)->entry.tqe_prev = (new_item);
-
}while(0);
-
-
-
printf("now, print again\n");
-
i = 0;
-
-
for((p) = ((&queue_head)->tqh_first); (p) != 0;
-
(p) = ((p)->entry.tqe_next))
-
{
-
printf("the %dth entry is %d\n", i, p->value);
-
}
-
-
-
p = (*(((struct queue_head_t *)((&queue_head)->tqh_last))->tqh_last));
-
printf("last is %d\n", p->value);
-
-
-
-
p = (*(((struct queue_head_t *)((p)->entry.tqe_prev))->tqh_last));
-
printf("the entry before last is %d\n", p->value);
-
}
代碼中有一些註釋,不懂的可以看看。其實對於鏈表操作,別人用文字說再多都對自己理解幫助不大。只有自己動手一步步把鏈表操作都畫出來,這樣才能完全理解。
特殊指針操作:
最後那兩個操作宏函數有點難理解,現在來講一下。在講之前,先看一個關於C語言指針的例子。
-
#include<stdio.h>
-
-
struct item_t
-
{
-
int a;
-
int b;
-
int c;
-
};
-
-
struct entry_t
-
{
-
int a;
-
int b;
-
};
-
-
-
int main()
-
{
-
struct item_t item = { 1, 2, 3};
-
-
entry_t *p = (entry_t*)(&item.b);
-
printf("a = %d, b = %d\n", p->a, p->b);
-
-
return 0;
-
}
代碼輸出的結果是:a = 2, b = 3
對於entry_t *p, 指針p指向的內存地址爲&item.b。此時對於編譯器來說,它認爲從&item.b這個地址開始,是一個entry_t結構體的內存區域。並且把前4個字節當作entry_t成員變量a的值,後4個字節當作entry_t成員變量b的值。所以就有了a = 2, b = 3這個輸出。
好了,現在開始講解那兩個難看懂的宏。先看一張圖。
雖然本文最前面的圖佈局更好看一點,但這張圖才更能反映文中這兩個結構體的內存佈局。不錯,tqe_next是在tqe_prev的前面。這使得tqe_next、tqe_prev於tqh_first、tqh_last的內存佈局一樣。一級指針在前,二級指針在後。
現在來解析代碼中最後兩個宏函數。
隊尾節點:
-
-
p = (*(((struct queue_head_t *)((&queue_head)->tqh_last))->tqh_last));
首先是(&queue_head)->tqh_last,它的值是最後一個元素的tqe_next這個成員變量的地址。然後把這個值強制轉換成struct queue_head_t *指針。此時,相當於有一個匿名的struct queue_head_t類型指針q。它指向的地址爲隊列的最後一個節點的tqe_next成員變量的地址。無論一級還是二級指針,其都是指向另外一個地址。只是二級指針只能指向一個一級指針的地址。
此時,在編譯器看來,從tqe_next這個變量的地址開始,是一個struct queue_head_t結構體的內存區域。並且可以將代碼簡寫成:
回想一下剛纔的那個例子。q->tqh_last的值就是上圖中最後一個節點的tqe_prev成員變量的值。所以*(q->tqh_last))就相當於*tqe_prev。注意,變量tqe_prev是一個二級指針,它指向倒數第二個節點的tqe_next成員。所以*tqe_prev獲取了倒數第二個節點的tqe_next成員的值。它的值就是最後一個節點的地址。最後,將這個地址賦值給p,此時p指向最後一個節點。完成了任務。好複雜的過程。
前一個節點:
現在來看一下最後那個宏函數,代碼如下:
-
-
p = (*(((struct queue_head_t *)((p)->entry.tqe_prev))->tqh_last));
注意,右邊的p此時是指向最後一個節點(元素)的。所以(p)->entry.tqe_prev就是倒數第二個節點tqe_next成員的地址。然後又強制轉換成struct queue_head_t指針。同樣,假設一個匿名的struct queue_head_t *q;此時,宏函數可以轉換成:
同樣,在編譯器看來,從倒數第二個參數節點tqe_next的地址開始,是一個structqueue_head_t結構體的內存區域。所以tqh_last實際值是tqe_prev變量上的值,即tqe_prev指向的地址。*((q)->tqh_last)就是*tqe_prev,即獲取tqe_prev指向的倒數第三個節點的tqe_next的值。而該值正是倒數第二個節點的地址。將這個地址賦值給p,此時,p就指向了倒數第二個節點。完成了TAILQ_PREV函數名的功能。
這個過程確實有點複雜。而且還涉及到強制類型轉換。
其實,在TAILQ_LAST(&queue_head, queue_head_t);中,既然都可以獲取最後一個節點的tqe_next的地址值,那麼直接將該值 + 4就可以得到tqe_precv的地址值了(假設爲pp)。有了該地址值pp,那麼直接**pp就可以得到最後一個節點的地址了。代碼如下:
-
struct queue_entry_t **pp = (&queue_head)->tqh_last;
-
pp += 1;
-
-
-
-
struct queue_entry_t ***ppp = (struct queue_entry_t ***)pp;
-
-
s = **ppp;
-
printf("the last is %d\n", s->value);
該代碼雖然能得到正確的結果,但總感覺直接加上一個偏移量的方式太粗暴了。
有一點要提出,+1那裏並不會因爲在64位的系統就不能運行,一樣能正確運行的。因爲1不是表示一個字節,而是一個指針的偏移量。在64位的系統上一個指針的偏移量爲8字節。這種”指針 + 數值”,實際其增加的值爲:數值 + sizeof(*指針)。不信的話,可以試一下char指針、int指針、結構體指針(結構體要有多個成員)。
好了,還是回到最開始的問題上吧。這個TAILQ_QUEUE隊列是由兩部分組成:隊列頭和隊列節點。在Libevent中,隊列頭一般是event_base結構體的一個成員變量,而隊列節點則是event結構體。比如event_base結構體裏面有一個struct event_list eventqueue;其中,結構體struct event_list如下定義:
-
-
TAILQ_HEAD (event_list, event);
-
-
-
struct event_list
-
{
-
struct event *tqh_first;
-
struct event **tqh_last;
-
};
在event結構體中,則有幾個TAILQ_ENTRY(event)類型的成員變量。這是因爲根據不同的條件,採用不同的隊列把這些event結構體連在一起,放到一條隊列中。