android源代碼學習 init中的雙向鏈表listnode

      在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

相關文章
相關標籤/搜索