listnode
読み:リストノード
外語:listnode
Androidで使われている連結リスト。この構造体のほか、関連する関数やマクロなどが多数定義されている。
書式
#include <cutils/list.h>
概要
定義
struct listnodeは、Linuxで実装された画期的な連結リストlist_headと同様の機能を持った連結リストである。
Linux用のlist_headがGPLv2なのに対して、Android用のlistnodeはApache License 2.0としてフルスクラッチで書き直されたものであるが、基本的な設計思想は大差がない。Androidの場合、カーネル層は一般にLinuxカーネルなのでlist_headが使えるが、ユーザー空間以上ではこのlistnodeを使うことになる。
具体的には次のように定義されている。
struct listnode
{
struct listnode *next;
struct listnode *prev;
};
前と次へのポインターしかない。これでどうやって目的のデータを数珠繋ぎにするのか。それが、このstruct listnodeと、その元祖であるstruct list_headの持つ、最大の特徴となる。
発想
普通に連結リストを作ろうとしたなら、普通のプログラマーであれば前と次へのポインターの他に、実際のデータか、またはデータへのポインターを構造体の中に含めるだろう。
こうして、この管理用の構造体の中に繋ぎたいデータを「含む」ことで、データは数珠繋ぎの連結リストとなる。
しかしこのstruct listnodeと、その元祖であるstruct list_headは違う。
簡単に説明すると、struct listnodeは、リストとして繋ぎたい要素を「含む」のではなく、リストとして繋ぎたい要素に「埋め込む」のである。
使い方
以下は、struct list_headとの違いに触れながら、struct listnodeの使い方を説明する。
挿入
繋ぎたい構造体の中に、struct listnodeを埋め込む。
struct data_struct {
int id;
char name[128];
struct listnode node;
}
通常の発想のものなら構造体の先頭ポインターで管理するところだが、struct listnodeの場合は、構造体の中のどこかにあればよい。どこでも良い。
この場合、struct listnode nodeのアドレスがリストに追加されることになる。構造体の先頭アドレスではない。
先頭
一つのリストごとに一つ、ソース中のどこかに、先頭に相当するstruct listnodeを定義する必要がある。以降、ここではこれを「先頭アイテム」と呼ぶことにする。
複数の関数で使うことが多いため、一般にはグローバル変数として定義して用いる。
struct listnode data_list;
先頭アイテムの初期化は次のようにする。
list_init(&data_list);
ここで使われるlist_initは関数で、system/core/libcutils/list.cで、次のように定義される。
void list_init(struct listnode *node)
{
node->next = node;
node->prev = node;
}
また、元祖list_headにあるLIST_HEAD()マクロに相当のマクロとして、list_declareもある。
list_declare(data_list);
このマクロは、system/core/include/cutils/list.h で次のように定義されている。
#define list_declare(name) \
struct listnode name = { \
.next = &name, \
.prev = &name, \
}
ただこのマクロは人気がないらしく、Androidでは使われている形跡がない。
list_init()関数での初期化またはlist_declare()での定義時点で、prevとnextには与えられたstruct listnodeのポインターが代入されることが分かる。nextを見て、そのポインターが先頭に相当するstruct listnodeなら、それ以降はないものとして判断できる。
node_to_item
構造体中のstruct list_headのアドレス(ノード)から、その構造体(アイテム)の先頭アドレスを得るためのマクロnode_to_itemが定義されている。従って、struct listnodeは構造体の中のどこにあってもよい。これは元祖list_headではlist_entryに対応する。
#define node_to_item(node, container, member) \
(container *) (((char*) (node)) - offsetof(container, member))
offsetofは stddef.h で定義される。AndroidのLinuxカーネルでは external/kernel-headers/original/linux/stddef.h に存在する。
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
登録するリストがstruct listnode data_list;として定義されているとすると、次のように使う。
struct listnode *node;
struct data_struct *item;
for (node = data_list.next; node != &data_list; node = node->next) {
item = node_to_item(node, struct data_struct, node);
printf("%d\n", item->id);
}
list_for_each
なお、無限ループしないように順番に見ていく場合は、次のマクロを使うのが便利である。
#define list_for_each(node, list) \
for (node = (list)->next; node != (list); node = node->next)
#define list_for_each_reverse(node, list) \
for (node = (list)->prev; node != (list); node = node->prev)
実体はfor文だが、要素二つだけのマクロとなるため、読みやすくなる。こうなる。
struct listnode *node;
struct data_struct *item;
list_for_each(node, &data_list) {
item = node_to_item(node, struct data_struct, node);
printf("%d\n", item->id);
}
再検索