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

相关文章
相关标签/搜索