NetBSD:/usr/include/sys/queue.h

$Id: queue.html,v 1.18 2017/10/02 07:51:43 ryo Exp $

sys/queue.h

queue.h ではマクロによってリスト構造とそのメソッドが定義されている。

すべてのリスト(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);
	}
などなど。


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)))))

LIST

LIST_EMPTY

LIST_1


SLIST

#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 は小さなリストでしか使ってはいけません。

SLIST

SLIST_EMPTY

SLIST_1


STAILQ

SIMPLEQとまったく同じ。移植時のコンパチビリティのために存在する?
よくわかりません ><


SIMPLEQ

#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 も不可能。
これも先頭から辿ればできなくはないけど。

SIMPLEQ

SIMPLEQ_EMPTY

SIMPLEQ_1


TAILQ

#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)を加えたもの。
その他の構造はほぼ LIST と同じ。

しかし、たったこれだけの変更で、常に 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))

構造的には、かなり万能なリスト構造である。無理に欠点を探すとしたら

ということくらい。

TAILQ

TAILQ_EMPTY

TAILQ_1


CIRCLEQ

#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 に関する全ての情報を持っているので、前後、先頭末尾と、 好きに辿ることができる。

CIRCLEQ

CIRCLEQ_EMPTY

CIRCLEQ_1


おまけ

各queue毎でできる、できないの違うもの

NetBSDのqueue.h

  LIST SLIST STAILQ
(=SIMPLEQ)
TAILQ CIRCLEQ
headerのサイズ sizeof(void*)*1sizeof(void*)*1sizeof(void*)*2 sizeof(void*)*2sizeof(void*)*2
elementのサイズ sizeof(void*)*2sizeof(void*)*1sizeof(void*)*1 sizeof(void*)*2sizeof(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)
※は、実装されてないが実装可能なもの、またはNetBSDには無いけどFreeBSDには存在するもの
O(n)は、リストを嘗める等で無理矢理実現されているもの。使うべきではありません



そして各操作において、headも一緒に指定する必要があるかどうかの表
  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
※マクロ的にはheadは必要としていないが、先頭かどうかを判別するためにはheadとの比較がが必要という、すごく変な実装。というか、これを怠るとバグるのでhead必須と同じ

headを知らなくてもREMOVEできるという点では、LISTはとても使いやすい。




FreeBSDのqueue.hやOpenBSDのqueue.hも似ているようで微妙に違ったりしています。
一度見てみましょう。

結論

単純な用件であればLISTで十分。まずこれで実現可能か考える。

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は要らない子。


MAIL