[glib/th/gdbus-unsubscribe-right-away: 1/3] add c-list.h for internal API of circular, intrusive, doubly linked list
- From: Thomas Haller <thaller src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/th/gdbus-unsubscribe-right-away: 1/3] add c-list.h for internal API of circular, intrusive, doubly linked list
- Date: Wed, 31 Mar 2021 20:24:01 +0000 (UTC)
commit ffe32f809a7126b390b3627967ab942e84232ec4
Author: Thomas Haller <thaller redhat com>
Date: Wed Mar 31 21:03:03 2021 +0200
add c-list.h for internal API of circular, intrusive, doubly linked list
glib/c-list.h | 443 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
glib/meson.build | 1 +
2 files changed, 444 insertions(+)
---
diff --git a/glib/c-list.h b/glib/c-list.h
new file mode 100644
index 000000000..c92aa6f3a
--- /dev/null
+++ b/glib/c-list.h
@@ -0,0 +1,443 @@
+#pragma once
+
+/*
+ * Circular Intrusive Double Linked List Collection in ISO-C11
+ *
+ * This implements a generic circular double linked list. List entries must
+ * embed the CList object, which provides pointers to the next and previous
+ * element. Insertion and removal can be done in O(1) due to the double links.
+ * Furthermore, the list is circular, thus allows access to front/tail in O(1)
+ * as well, even if you only have a single head pointer (which is not how the
+ * list is usually operated, though).
+ *
+ * Note that you are free to use the list implementation without a head
+ * pointer. However, usual operation uses a single CList object as head, which
+ * is itself linked in the list and as such must be identified as list head.
+ * This allows very simply list operations and avoids a lot of special cases.
+ * Most importantly, you can unlink entries without requiring a head pointer.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+
+typedef struct CList CList;
+
+/**
+ * struct CList - Entry of a circular double linked list
+ * @next: next entry
+ * @prev: previous entry
+ *
+ * Each entry in a list must embed a CList object. This object contains
+ * pointers to its next and previous elements, which can be freely accessed by
+ * the API user at any time. Note that the list is circular, and the list head
+ * is linked in the list as well.
+ *
+ * The list head must be initialized via C_LIST_INIT before use. There is no
+ * reason to initialize entry objects before linking them. However, if you need
+ * a boolean state that tells you whether the entry is linked or not, you should
+ * initialize the entry via C_LIST_INIT as well.
+ */
+struct CList {
+ CList *next;
+ CList *prev;
+};
+
+#define C_LIST_INIT(_var) { .next = &(_var), .prev = &(_var) }
+
+/**
+ * c_list_init() - initialize list entry
+ * @what: list entry to initialize
+ */
+static inline void c_list_init(CList *what) {
+ *what = (CList)C_LIST_INIT(*what);
+}
+
+/**
+ * c_list_entry() - get parent container of list entry
+ * @_what: list entry, or NULL
+ * @_t: type of parent container
+ * @_m: member name of list entry in @_t
+ *
+ * If the list entry @_what is embedded into a surrounding structure, this will
+ * turn the list entry pointer @_what into a pointer to the parent container
+ * (using offsetof(3), or sometimes called container_of(3)).
+ *
+ * If @_what is NULL, this will also return NULL.
+ *
+ * Return: Pointer to parent container, or NULL.
+ */
+#define c_list_entry(_what, _t, _m) \
+ ((_t *)(void *)(((unsigned long)(void *)(_what) ?: \
+ offsetof(_t, _m)) - offsetof(_t, _m)))
+
+/**
+ * c_list_is_linked() - check whether an entry is linked
+ * @what: entry to check, or NULL
+ *
+ * Return: True if @what is linked in a list, false if not.
+ */
+static inline _Bool c_list_is_linked(const CList *what) {
+ return what && what->next != what;
+}
+
+/**
+ * c_list_is_empty() - check whether a list is empty
+ * @list: list to check, or NULL
+ *
+ * This is the same as !c_list_is_linked().
+ *
+ * Return: True if @list is empty, false if not.
+ */
+static inline _Bool c_list_is_empty(const CList *list) {
+ return !c_list_is_linked(list);
+}
+
+/**
+ * c_list_link_before() - link entry into list
+ * @where: linked list entry used as anchor
+ * @what: entry to link
+ *
+ * This links @what directly in front of @where. @where can either be a list
+ * head or any entry in the list.
+ *
+ * If @where points to the list head, this effectively links @what as new tail
+ * element. Hence, the macro c_list_link_tail() is an alias to this.
+ *
+ * @what is not inspected prior to being linked. Hence, it better not be linked
+ * into another list, or the other list will be corrupted.
+ */
+static inline void c_list_link_before(CList *where, CList *what) {
+ CList *prev = where->prev, *next = where;
+
+ next->prev = what;
+ what->next = next;
+ what->prev = prev;
+ prev->next = what;
+}
+#define c_list_link_tail(_list, _what) c_list_link_before((_list), (_what))
+
+/**
+ * c_list_link_after() - link entry into list
+ * @where: linked list entry used as anchor
+ * @what: entry to link
+ *
+ * This links @what directly after @where. @where can either be a list head or
+ * any entry in the list.
+ *
+ * If @where points to the list head, this effectively links @what as new front
+ * element. Hence, the macro c_list_link_front() is an alias to this.
+ *
+ * @what is not inspected prior to being linked. Hence, it better not be linked
+ * into another list, or the other list will be corrupted.
+ */
+static inline void c_list_link_after(CList *where, CList *what) {
+ CList *prev = where, *next = where->next;
+
+ next->prev = what;
+ what->next = next;
+ what->prev = prev;
+ prev->next = what;
+}
+#define c_list_link_front(_list, _what) c_list_link_after((_list), (_what))
+
+/**
+ * c_list_unlink_stale() - unlink element from list
+ * @what: element to unlink
+ *
+ * This unlinks @what. If @what was initialized via C_LIST_INIT(), it has no
+ * effect. If @what was never linked, nor initialized, behavior is undefined.
+ *
+ * Note that this does not modify @what. It just modifies the previous and next
+ * elements in the list to no longer reference @what. If you want to make sure
+ * @what is re-initialized after removal, use c_list_unlink().
+ */
+static inline void c_list_unlink_stale(CList *what) {
+ CList *prev = what->prev, *next = what->next;
+
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * c_list_unlink() - unlink element from list and re-initialize
+ * @what: element to unlink
+ *
+ * This is like c_list_unlink_stale() but re-initializes @what after removal.
+ */
+static inline void c_list_unlink(CList *what) {
+ /* condition is not needed, but avoids STOREs in fast-path */
+ if (c_list_is_linked(what)) {
+ c_list_unlink_stale(what);
+ *what = (CList)C_LIST_INIT(*what);
+ }
+}
+
+/**
+ * c_list_swap() - exchange the contents of two lists
+ * @list1: the list to operate on
+ * @list2: the list to operate on
+ *
+ * This replaces the contents of the list @list1 with the contents
+ * of @list2, and vice versa.
+ */
+static inline void c_list_swap(CList *list1, CList *list2) {
+ CList t;
+
+ /* make neighbors of list1 point to list2, and vice versa */
+ t = *list1;
+ t.next->prev = list2;
+ t.prev->next = list2;
+ t = *list2;
+ t.next->prev = list1;
+ t.prev->next = list1;
+
+ /* swap list1 and list2 now that their neighbors were fixed up */
+ t = *list1;
+ *list1 = *list2;
+ *list2 = t;
+}
+
+/**
+ * c_list_splice() - splice one list into another
+ * @target: the list to splice into
+ * @source: the list to splice
+ *
+ * This removes all the entries from @source and splice them into @target.
+ * The order of the two lists is preserved and the source is appended
+ * to the end of target.
+ *
+ * On return, the source list will be empty.
+ */
+static inline void c_list_splice(CList *target, CList *source) {
+ if (!c_list_is_empty(source)) {
+ /* attach the front of @source to the tail of @target */
+ source->next->prev = target->prev;
+ target->prev->next = source->next;
+
+ /* attach the tail of @source to the front of @target */
+ source->prev->next = target;
+ target->prev = source->prev;
+
+ /* clear source */
+ *source = (CList)C_LIST_INIT(*source);
+ }
+}
+
+/**
+ * c_list_first() - return pointer to first element, or NULL if empty
+ * @list: list to operate on, or NULL
+ *
+ * This returns a pointer to the first element, or NULL if empty. This never
+ * returns a pointer to the list head.
+ *
+ * Return: Pointer to first list element, or NULL if empty.
+ */
+static inline CList *c_list_first(CList *list) {
+ return c_list_is_empty(list) ? NULL : list->next;
+}
+
+/**
+ * c_list_last() - return pointer to last element, or NULL if empty
+ * @list: list to operate on, or NULL
+ *
+ * This returns a pointer to the last element, or NULL if empty. This never
+ * returns a pointer to the list head.
+ *
+ * Return: Pointer to last list element, or NULL if empty.
+ */
+static inline CList *c_list_last(CList *list) {
+ return c_list_is_empty(list) ? NULL : list->prev;
+}
+
+/**
+ * c_list_first_entry() - return pointer to first entry, or NULL if empty
+ * @_list: list to operate on, or NULL
+ * @_t: type of list entries
+ * @_m: name of CList member in @_t
+ *
+ * This is like c_list_first(), but also applies c_list_entry() on the result.
+ *
+ * Return: Pointer to first list entry, or NULL if empty.
+ */
+#define c_list_first_entry(_list, _t, _m) \
+ c_list_entry(c_list_first(_list), _t, _m)
+
+/**
+ * c_list_last_entry() - return pointer to last entry, or NULL if empty
+ * @_list: list to operate on, or NULL
+ * @_t: type of list entries
+ * @_m: name of CList member in @_t
+ *
+ * This is like c_list_last(), but also applies c_list_entry() on the result.
+ *
+ * Return: Pointer to last list entry, or NULL if empty.
+ */
+#define c_list_last_entry(_list, _t, _m) \
+ c_list_entry(c_list_last(_list), _t, _m)
+
+/**
+ * c_list_for_each*() - iterators
+ *
+ * The c_list_for_each*() macros provide simple for-loop wrappers to iterate
+ * a linked list. They come in a set of flavours:
+ *
+ * - "entry": This combines c_list_entry() with the loop iterator, so the
+ * iterator always has the type of the surrounding object, rather
+ * than CList.
+ *
+ * - "safe": The loop iterator always keeps track of the next element to
+ * visit. This means, you can safely modify the current element,
+ * while retaining loop-integrity.
+ * You still must not touch any other entry of the list. Otherwise,
+ * the loop-iterator will be corrupted.
+ *
+ * - "continue": Rather than starting the iteration at the front of the list,
+ * use the current value of the iterator as starting position.
+ * Note that the first loop iteration will be the following
+ * element, not the given element.
+ *
+ * - "unlink": This unlinks the current element from the list before the loop
+ * code is run. Note that this only does a partial unlink, since
+ * it assumes the entire list will be unlinked. You must not
+ * break out of the loop, or the list will be in an inconsistent
+ * state.
+ */
+
+#define c_list_for_each(_iter, _list) \
+ for (_iter = (_list)->next; \
+ (_iter) != (_list); \
+ _iter = (_iter)->next)
+
+#define c_list_for_each_entry(_iter, _list, _m) \
+ for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m); \
+ &(_iter)->_m != (_list); \
+ _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m))
+
+#define c_list_for_each_safe(_iter, _safe, _list) \
+ for (_iter = (_list)->next, _safe = (_iter)->next; \
+ (_iter) != (_list); \
+ _iter = (_safe), _safe = (_safe)->next)
+
+#define c_list_for_each_entry_safe(_iter, _safe, _list, _m) \
+ for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \
+ _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \
+ &(_iter)->_m != (_list); \
+ _iter = (_safe), \
+ _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \
+
+#define c_list_for_each_continue(_iter, _list) \
+ for (_iter = (_iter) ? (_iter)->next : (_list)->next; \
+ (_iter) != (_list); \
+ _iter = (_iter)->next)
+
+#define c_list_for_each_entry_continue(_iter, _list, _m) \
+ for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next, \
+ __typeof__(*_iter), \
+ _m); \
+ &(_iter)->_m != (_list); \
+ _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m))
+
+#define c_list_for_each_safe_continue(_iter, _safe, _list) \
+ for (_iter = (_iter) ? (_iter)->next : (_list)->next, \
+ _safe = (_iter)->next; \
+ (_iter) != (_list); \
+ _iter = (_safe), _safe = (_safe)->next)
+
+#define c_list_for_each_entry_safe_continue(_iter, _safe, _list, _m) \
+ for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next, \
+ __typeof__(*_iter), \
+ _m), \
+ _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \
+ &(_iter)->_m != (_list); \
+ _iter = (_safe), \
+ _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \
+
+#define c_list_for_each_safe_unlink(_iter, _safe, _list) \
+ for (_iter = (_list)->next, _safe = (_iter)->next; \
+ ((*_iter = (CList)C_LIST_INIT(*_iter)), (_iter) != (_list)); \
+ _iter = (_safe), _safe = (_safe)->next)
+
+#define c_list_for_each_entry_safe_unlink(_iter, _safe, _list, _m) \
+ for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \
+ _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \
+ (((_iter)->_m = (CList)C_LIST_INIT((_iter)->_m)), \
+ &(_iter)->_m != (_list)); \
+ _iter = (_safe), \
+ _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \
+
+/**
+ * c_list_flush() - flush all entries from a list
+ * @list: list to flush
+ *
+ * This unlinks all entries from the given list @list and reinitializes their
+ * link-nodes via C_LIST_INIT().
+ *
+ * Note that the entries are not modified in any other way, nor is their memory
+ * released. This function just unlinks them and resets all the list nodes. It
+ * is particularly useful with temporary lists on the stack in combination with
+ * the GCC-extension __attribute__((__cleanup__(arg))).
+ */
+static inline void c_list_flush(CList *list) {
+ CList *iter, *safe;
+
+ c_list_for_each_safe_unlink(iter, safe, list)
+ /* empty */ ;
+}
+
+/**
+ * c_list_length() - return number of linked entries, excluding the head
+ * @list: list to operate on
+ *
+ * Returns the number of entries in the list, excluding the list head @list.
+ * That is, for a list that is empty according to c_list_is_empty(), the
+ * returned length is 0. This requires to iterate the list and has thus O(n)
+ * runtime.
+ *
+ * Note that this function is meant for debugging purposes only. If you need
+ * the list size during normal operation, you should maintain a counter
+ * separately.
+ *
+ * Return: Number of items in @list.
+ */
+static inline unsigned long c_list_length(const CList *list) {
+ unsigned long n = 0;
+ const CList *iter;
+
+ c_list_for_each(iter, list)
+ ++n;
+
+ return n;
+}
+
+/**
+ * c_list_contains() - check whether an entry is linked in a certain list
+ * @list: list to operate on
+ * @what: entry to look for
+ *
+ * This checks whether @what is linked into @list. This requires a linear
+ * search through the list, as such runs in O(n). Note that the list-head is
+ * considered part of the list, and hence this returns true if @what equals
+ * @list.
+ *
+ * Note that this function is meant for debugging purposes, and consistency
+ * checks. You should always be aware whether your objects are linked in a
+ * specific list.
+ *
+ * Return: True if @what is in @list, false otherwise.
+ */
+static inline _Bool c_list_contains(const CList *list, const CList *what) {
+ const CList *iter;
+
+ c_list_for_each(iter, list)
+ if (what == iter)
+ return 1;
+
+ return what == list;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/glib/meson.build b/glib/meson.build
index 8c18e6de4..445f32d27 100644
--- a/glib/meson.build
+++ b/glib/meson.build
@@ -231,6 +231,7 @@ deprecated_sources = files(
)
glib_sources = files(
+ 'c-list.h',
'garcbox.c',
'garray.c',
'gasyncqueue.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]