android源代码学习 init中的双向链表listnode

      在init源代码中双向链表listnode被使用地很多。android源代码中定义了结构体listnode,奇怪的是,这个结构体只有用于链接节点的prev和next指针,却没有任何和”数据“有关的成员变量。那么代码中如何通过一个节点来找到该节点“存储“的数据呢?关键是下面这个宏。

#define node_to_item \
    (container *) (((char *) (node)) - offsetof(container, member))

      看懂了这个宏,就基本能够理解listnode的用法了。下面是一个我自己参考listnode写的小例子。

#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宏来建立链表节点与数据类型之间的联系。很有趣,可是为什么要这么做呢?

你可能感兴趣的:(android)