在init源代碼中雙向鏈表listnode被使用地不少。android源代碼中定義告終構體listnode,奇怪的是,這個結構體只有用於連接節點的prev和next指針,卻沒有任何和」數據「有關的成員變量。那麼代碼中如何經過一個節點來找到該節點「存儲「的數據呢?關鍵是下面這個宏。node
#define node_to_item \ (container *) (((char *) (node)) - offsetof(container, member))
看懂了這個宏,就基本可以理解listnode的用法了。下面是一個我本身參考listnode寫的小例子。android
#include <stdio.h> #include <stddef.h> // listnode類型的聲明,裏面只有兩個指針prev,next typedef struct _listnode { struct _listnode *prev; struct _listnode *next; } listnode; // 使用listnode時最關鍵的宏 #define node_to_item(node, container, member) \ (container *) (((char*) (node)) - offsetof(container, member)) // 給鏈表添加節點 void list_add_tail(listnode *list, listnode *node) { list->prev->next = node; node->prev = list->prev; node->next = list; list->prev = node; } // 每一個listnode節點對應「存儲」的數據信息,其中居然有一個listnode類型的成員變量? typedef struct _node { listnode list; int data; } node; // 創建一個有三個節點的雙向鏈表,並遍歷輸出一遍。 int main() { node n1, n2, n3, *n; listnode list, *p; n1.data = 1; n2.data = 2; n3.data = 3; list.prev = &list; list.next = &list; list_add_tail(&list, &n1.list); list_add_tail(&list, &n2.list); list_add_tail(&list, &n3.list); for(p = list.next; p != &list; p = p->next) { n = node_to_item(p, node, list); printf("%d\n", n->data); } return 0; }
上面這個例子遍歷了一次雙向鏈表,輸出結果是函數
1 2 3
在遍歷雙向鏈表時,使用了node_to_item宏,這個宏的做用是將一個listnode指針轉換成了一個指定類型的指針!這就是listnode有意思的地方。這個宏先使用offsetof函數獲取到指定結構體中指定成員變量的地址偏移量,而後經過指針運算得到listnode指針變量所在結構體變量的指針。指針
和教科書中的實現方法正好相反,listnode不是在節點當中聲明一個指向某種數據類型的指針,而是在數據類型中加入一個指向鏈表節點的指針,而後經過node_to_item宏來創建鏈表節點與數據類型之間的聯繫。頗有趣,但是爲何要這麼作呢?code