すべてのリスト(queue?)は、先頭、あるいは先頭と末尾の構造体(element)を指し示す 「???_HEAD」という名前のマクロで定義される構造体を持つ。
各要素(element)は、「???_ENTRY」というマクロを、使用する構造体の中にメンバとして持つ必要がある。
HEAD用の構造体 (HEAD) ↓ 要素用の構造体1 (含ENTRY) ↓ 要素用の構造体2 (含ENTRY) ↓ 要素用の構造体3 (含ENTRY) ↓ : ↓ 要素用の構造体N (含ENTRY)LIST を例に取って解説すると...
#define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ }以上が sys/queue.h に定義されている(宣言用の)マクロで、 実際に C で使うためには、以下のようにする。
/* HEAD の宣言と定義 */ LIST_HEAD(,my) my_head = LIST_HEAD_INITIALIZER(my_head); /* 構造体(element)の宣言 */ struct my { char *my_member1; short my_member2; LIST_ENTRY(my) my_list; /* LIST_ENTRY の場所はどこでも可 */ int my_member3; double my_member4; };あとは LIST_INSERT_AFTER, LIST_INSERT_BEFORE, LIST_REMOVE などのマクロを使って操作を行えばいい。
void my_add(void) { struct my *myp = malloc(sizeof(struct my)); memset(myp, 0, sizeof(struct my)); myp->my_member1 = ... myp->my_member2 = ... myp->my_member3 = ... LIST_INSERT_HEAD(my_head, myp,my_list); }
void my_remove(struct my *myp) { LIST_REMOVE(myp, my_list); }などなど。
#define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ }シンプルかつ必要十分なリスト構造。リストを前後に辿ることはできるが、リストを辿らずに末尾を知ることはできない。
LIST_HEAD は最初の element へのポインタのみ持つ。
ENTRY には、次の element へのポインタと、自分を指しているポインタへのポインタ
(前の element へのポインタで無いので注意)を持つ。
(自分を指しているポインタへのポインタは、INSERT_BEFORE のために必要)
LIST_PREV は存在しないが、がんばれば、LIST_PREV も実現できなくはない。
以下のようなマクロを使えば OK。(ただし head を知ってなければいけない)
#define OFFSETOF(type, member) ((size_t)(&((struct type *)0)->member)) #define LIST_PREV(head, type, elm, field) \ ((((struct type *)((elm)->field.le_prev)) == ((struct type *)(&(head)))) ? \ (struct type *)NULL : \ ((struct type *)(((char*)((elm)->field.le_prev))-(OFFSETOF(type,field)))))
#define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ }SLIST は、LIST から prev のポインタを省略したもの。
HEAD には最初の element へのポインタ、ENTRY には次の element へのポインタだけを持つ。
そのため、PREV も INSERT もできない。INSERT_HEAD と REMOVE_HEAD しかできないので、用途がかなり制限される。
しかし何故かマクロでは REMOVE が定義されている。
どうやって実装しているのかと思って中身を見ると、HEADから該当elementまでリストを辿るというアホなことをしている。
これはダメだろ常識的に考えて…。
SLIST_REMOVE は小さなリストでしか使ってはいけません。
#define SIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqh_first; /* first element */ \ struct type **sqh_last; /* addr of last next element */ \ } #define SIMPLEQ_ENTRY(type) \ struct { \ struct type *sqe_next; /* next element */ \ }末尾をわかるようにしたSLIST。
SIMPLEQ_HEAD は、最初と最後の element へのポインタを持っている。
ただし、最後のポインタ(sqh_last) は、最後の element の SIMPLEQ_ENTRY のポインタである。
SIMPLEQ_ENTRY には、次の element のポインタだけを持つ。LIST や他の queue のように
自分を差しているポインタへのポインタは持たない。
したがって、任意の element の前に element を挿入すること(SIMPLEQ_INSERT_BEFORE)はできない。
上述のように sqh_prev 相当のものを持たないので、SIMPLEQ_REMOVE さえできないことに注意。
SIMPLEQ_REMOVE_HEAD で、先頭から削除することしかできない。
もし任意の要素を REMOVE したいのならば、先頭から該当 element まで辿って…、みたいなことをしなければならない。
当然 SIMPLEQ_PREV も不可能。
これも先頭から辿ればできなくはないけど。
#define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ }LIST の HEAD に、末尾の element の TAILQ_ENTRY を指すポインタ(tqh_tail)を加えたもの。
しかし、たったこれだけの変更で、常に tqe_prev->tqe_prev と辿れることが保証され、
tqe_prev->tqe_prev->tqe_next と辿ることにより、一つ前の element を得ることができるようになってる。すごい。
(図参照。HEAD では tqe_next = tqh_first、tqe_prev = tqh_last であることを利用)
#define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
構造的には、かなり万能なリスト構造である。無理に欠点を探すとしたら
#define CIRCLEQ_HEAD(name, type) \ struct name { \ struct type *cqh_first; /* first element */ \ struct type *cqh_last; /* last element */ \ } #define CIRCLEQ_ENTRY(type) \ struct { \ struct type *cqe_next; /* next element */ \ struct type *cqe_prev; /* previous element */ \ }各ENTRY 内の prev の差し示す先を、「前の element の ENTRY へのポインタ」 の代わりに「前の element のポインタ」にしたもの。
一見便利そうだが、 先頭 element の ENTRY の prev が、HEAD を差してしまっていたり、 末尾 element の ENTRY の next が、HEAD を差してしまっていたりして、結構イリーガル。
つまり先頭 element から prev を辿るときに、その先が HEAD か element かで 場合分けしなければならないし、末尾の element から next を辿るときに、 その先が HEAD か element かで場合分けしなければならない。
しかも場合分けしなければならないということは、INSERTやREOVE時にHEADを知っておく必要がある。
つまり element だけが渡された関数内で、CIRCLEQ_INSERT や CIRCLEQ_REMOVE することができない。
微妙に使いにくい。
他の queue と違い、link に関する全ての情報を持っているので、前後、先頭末尾と、 好きに辿ることができる。
LIST | SLIST | STAILQ (=SIMPLEQ) | TAILQ | CIRCLEQ | |
headerのサイズ | sizeof(void*)*1 | sizeof(void*)*1 | sizeof(void*)*2 | sizeof(void*)*2 | sizeof(void*)*2 |
elementのサイズ | sizeof(void*)*2 | sizeof(void*)*1 | sizeof(void*)*1 | sizeof(void*)*2 | sizeof(void*)*2 |
_INIT | O(1) | O(1) | O(1) | O(1) | O(1) |
_EMPTY | O(1) | O(1) | O(1) | O(1) | O(1) |
_LAST | - | - | O(1)※ | O(1) | O(1) |
_FIRST | O(1) | O(1) | O(1) | O(1) | O(1) |
_NEXT | O(1) | O(1) | O(1) | O(1) | O(1) |
_PREV | O(1)※ | - | - | O(1) | O(1) |
_INSERT_HEAD | O(1) | O(1) | O(1) | O(1) | O(1) |
_INSERT_TAIL | - | - | O(1) | O(1) | O(1) |
_INSERT_AFTER | O(1) | O(1) | O(1) | O(1) | O(1) |
_INSERT_BEFORE | O(1) | - | - | O(1) | O(1) |
_REMOVE | O(1) | O(n) | O(n) | O(1) | O(1) |
_REMOVE_HEAD | O(1)※ | O(1) | O(1) | O(1)※ | O(1)※ |
_FOREACH | O(n) | O(n) | O(n) | O(n) | O(n) |
_FOREACH_REVERSE | - | - | - | O(n) | O(n) |
LIST | SLIST | STAILQ (=SIMPLEQ) | TAILQ | CIRCLEQ | |
_INIT | Yes | Yes | Yes | Yes | Yes |
_EMPTY | Yes | Yes | Yes | Yes | Yes |
_LAST | - | - | Yes | Yes | Yes |
_FIRST | Yes | Yes | Yes | Yes | Yes |
_NEXT | no | no | no | no | no |
_PREV | Yes | - | - | Yes | Yes(※) |
_INSERT_HEAD | Yes | Yes | Yes | Yes | Yes |
_INSERT_TAIL | - | - | Yes | Yes | Yes |
_INSERT_AFTER | no | no | Yes | Yes | Yes |
_INSERT_BEFORE | no | - | - | no | Yes |
_REMOVE | no | Yes | Yes | Yes | Yes |
_REMOVE_HEAD | Yes | Yes | Yes | Yes | Yes |
_FOREACH | Yes | Yes | Yes | Yes | Yes |
_FOREACH_REVERSE | - | - | - | Yes | Yes |
PREVを辿ったり、最後尾にアクセスしたければTAILQを使う
各要素中のエントリを小さくしたいのであれば、SLIST/SIMPLEQを考える。
ただし、SLIST/STAILQ/SIMPLEQのREMOVEはコストが高いので事実上小さなリストでしか使えない。
巨大なリストでは REMOVE_HEAD しか使えないと思うべし。
SLISTとSTAILQはENTRYにポインタが一つだけなのが利点(たぶん)。 各要素を小さくしつつ、FILO を構成したい時はSLIST。 各要素を小さくしつつ、FIFO/FILO (INSERT_HEAD, INSERT_TAIL, REMOVE_HEAD) したい時は STAILQ, SIMPLEQ。
TAILQがあればCIRCLEQは要らない子。