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