├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── AUTHORS ├── NEWS.md ├── README.md ├── meson.build └── src ├── c-list.h ├── meson.build ├── test-api.c ├── test-basic.c └── test-embed.c /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [*.{c,h}] 10 | indent_style = space 11 | indent_size = 8 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * *' 8 | 9 | jobs: 10 | ci-linux: 11 | name: Linux CI 12 | uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 13 | with: 14 | cabuild_ref: "v1" 15 | linux: true 16 | m32: true 17 | matrixmode: true 18 | valgrind: true 19 | ci-macos: 20 | name: MacOS CI 21 | uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 22 | with: 23 | cabuild_ref: "v1" 24 | linux: false 25 | macos: true 26 | ci-windows: 27 | name: Windows CI 28 | uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 29 | with: 30 | cabuild_ref: "v1" 31 | linux: false 32 | windows: true 33 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | LICENSE: 2 | This project is dual-licensed under both the Apache License, Version 3 | 2.0, and the GNU Lesser General Public License, Version 2.1+. 4 | 5 | AUTHORS-ASL: 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | AUTHORS-LGPL: 19 | This program is free software; you can redistribute it and/or modify it 20 | under the terms of the GNU Lesser General Public License as published 21 | by the Free Software Foundation; either version 2.1 of the License, or 22 | (at your option) any later version. 23 | 24 | This program is distributed in the hope that it will be useful, but 25 | WITHOUT ANY WARRANTY; without even the implied warranty of 26 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 | Lesser General Public License for more details. 28 | 29 | You should have received a copy of the GNU Lesser General Public License 30 | along with this program; If not, see . 31 | 32 | COPYRIGHT: (ordered alphabetically) 33 | Copyright (C) 2015-2022 Red Hat, Inc. 34 | 35 | AUTHORS: (ordered alphabetically) 36 | Danilo Horta 37 | David Rheinsberg 38 | Lucas De Marchi 39 | Michele Dionisio 40 | Thomas Haller 41 | Tom Gundersen 42 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # c-list - Circular Intrusive Double Linked List Collection 2 | 3 | ## CHANGES WITH 3.1.0: 4 | 5 | * The minimum required meson version is now 0.60.0. 6 | 7 | * New function c_list_split() is added. It reverses c_list_splice() 8 | and thus allows to split a list in half. 9 | 10 | Contributions from: David Rheinsberg, Michele Dionisio 11 | 12 | - Brno, 2022-06-22 13 | 14 | ## CHANGES WITH 3: 15 | 16 | * API break: The c_list_loop_*() symbols were removed, since we saw 17 | little use for them. No user was known at the time, so 18 | all projects should build with the new API version 19 | unchanged. 20 | Since c-list does not distribute any compiled code, there 21 | is no ABI issue with this change. 22 | 23 | * Two new symbols c_list_length() and c_list_contains(). They are meant 24 | for debugging purposes, to easily verify list integrity. Since they 25 | run in O(n) time, they are not recommended for any other use than 26 | debugging. 27 | 28 | * New symbol c_list_init() is provided as alternative to the verbose 29 | C_LIST_INIT assignment. 30 | 31 | * The c-list API is extended to work well with `const CList` objects. 32 | That is, any read-only accessor function allows constant objects as 33 | input now. 34 | Note that this does not propagate into other members linked in the 35 | list. Using `const` for CList members is of little practical use. 36 | However, it might be of use for its embedding objects, so we now 37 | allow it in the CList API as well. 38 | 39 | * The c_list_splice() call now clears the source list, rather than 40 | returning with stale pointers. Technically, this is also an API 41 | break, but unlikely to affect any existing code. 42 | 43 | Contributions from: David Herrmann, Thomas Haller 44 | 45 | - Berlin, 2017-08-13 46 | 47 | ## CHANGES WITH 2: 48 | 49 | * Adjust project-name in build-system to reflect the actual project. The 50 | previous releases incorrectly claimed to be c-rbtree in the build 51 | system. 52 | 53 | * Add c_list_swap() that swaps two lists given their head pointers. 54 | 55 | * Add c_list_splice() that moves a list. 56 | 57 | * Add LGPL2.1+ as license so c-list can be imported into GPL2 projects. 58 | It is now officially dual-licensed. 59 | 60 | * As usual a bunch of fixes, additional tests, and documentation 61 | updates. 62 | 63 | Contributions from: David Herrmann, Tom Gundersen 64 | 65 | - Lund, 2017-05-03 66 | 67 | ## CHANGES WITH 1: 68 | 69 | * Initial release of c-list. 70 | 71 | * This project provides an implementation of a circular double linked 72 | list in standard ISO-C11. License is ASL-2.0 and the build system 73 | used is `Meson'. 74 | 75 | Contributions from: David Herrmann, Tom Gundersen 76 | 77 | - Berlin, 2017-03-03 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | c-list 2 | ====== 3 | 4 | Circular Intrusive Double Linked List Collection 5 | 6 | The c-list project implements an intrusive collection based on circular double 7 | linked lists in ISO-C11. It aims for minimal API constraints, leaving maximum 8 | control over the data-structures to the API consumer. 9 | 10 | ### Project 11 | 12 | * **Website**: 13 | * **Bug Tracker**: 14 | 15 | ### Requirements 16 | 17 | The requirements for this project are: 18 | 19 | * `libc` (e.g., `glibc >= 2.16`) 20 | 21 | At build-time, the following software is required: 22 | 23 | * `meson >= 0.60` 24 | * `pkg-config >= 0.29` 25 | 26 | ### Build 27 | 28 | The meson build-system is used for this project. Contact upstream 29 | documentation for detailed help. In most situations the following 30 | commands are sufficient to build and install from source: 31 | 32 | ```sh 33 | mkdir build 34 | cd build 35 | meson setup .. 36 | ninja 37 | meson test 38 | ninja install 39 | ``` 40 | 41 | No custom configuration options are available. 42 | 43 | ### Repository: 44 | 45 | - **web**: 46 | - **https**: `https://github.com/c-util/c-list.git` 47 | - **ssh**: `git@github.com:c-util/c-list.git` 48 | 49 | ### License: 50 | 51 | - **Apache-2.0** OR **LGPL-2.1-or-later** 52 | - See AUTHORS file for details. 53 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'c-list', 3 | 'c', 4 | default_options: [ 5 | 'c_std=c99', 6 | ], 7 | license: 'Apache', 8 | meson_version: '>=0.60.0', 9 | version: '3.1.0', 10 | ) 11 | major = meson.project_version().split('.')[0] 12 | project_description = 'Circular Intrusive Double Linked List Collection' 13 | 14 | mod_pkgconfig = import('pkgconfig') 15 | 16 | # See c-stdaux for details on these. We do not have c-stdaux as dependency, so 17 | # we keep a duplicated set here, reduced to the minimum. 18 | cflags = meson.get_compiler('c').get_supported_arguments( 19 | '-D_GNU_SOURCE', 20 | 21 | '-Wno-gnu-alignof-expression', 22 | '-Wno-maybe-uninitialized', 23 | '-Wno-unknown-warning-option', 24 | '-Wno-unused-parameter', 25 | 26 | '-Wno-error=type-limits', 27 | '-Wno-error=missing-field-initializers', 28 | 29 | '-Wdate-time', 30 | '-Wdeclaration-after-statement', 31 | '-Wlogical-op', 32 | '-Wmissing-include-dirs', 33 | '-Wmissing-noreturn', 34 | '-Wnested-externs', 35 | '-Wredundant-decls', 36 | '-Wshadow', 37 | '-Wstrict-aliasing=3', 38 | '-Wsuggest-attribute=noreturn', 39 | '-Wundef', 40 | '-Wwrite-strings', 41 | ) 42 | add_project_arguments(cflags, language: 'c') 43 | 44 | subdir('src') 45 | 46 | meson.override_dependency('libclist-'+major, libclist_dep, static: true) 47 | -------------------------------------------------------------------------------- /src/c-list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Circular Intrusive Double Linked List Collection in ISO-C11 5 | * 6 | * This implements a generic circular double linked list. List entries must 7 | * embed the CList object, which provides pointers to the next and previous 8 | * element. Insertion and removal can be done in O(1) due to the double links. 9 | * Furthermore, the list is circular, thus allows access to front/tail in O(1) 10 | * as well, even if you only have a single head pointer (which is not how the 11 | * list is usually operated, though). 12 | * 13 | * Note that you are free to use the list implementation without a head 14 | * pointer. However, usual operation uses a single CList object as head, which 15 | * is itself linked in the list and as such must be identified as list head. 16 | * This allows very simply list operations and avoids a lot of special cases. 17 | * Most importantly, you can unlink entries without requiring a head pointer. 18 | */ 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | typedef struct CList CList; 28 | 29 | /** 30 | * struct CList - Entry of a circular double linked list 31 | * @next: next entry 32 | * @prev: previous entry 33 | * 34 | * Each entry in a list must embed a CList object. This object contains 35 | * pointers to its next and previous elements, which can be freely accessed by 36 | * the API user at any time. Note that the list is circular, and the list head 37 | * is linked in the list as well. 38 | * 39 | * The list head must be initialized via C_LIST_INIT before use. There is no 40 | * reason to initialize entry objects before linking them. However, if you need 41 | * a boolean state that tells you whether the entry is linked or not, you should 42 | * initialize the entry via C_LIST_INIT as well. 43 | */ 44 | struct CList { 45 | CList *next; 46 | CList *prev; 47 | }; 48 | 49 | #define C_LIST_INIT(_var) { .next = &(_var), .prev = &(_var) } 50 | 51 | /** 52 | * c_list_init() - initialize list entry 53 | * @what: list entry to initialize 54 | * 55 | * Return: @what is returned. 56 | */ 57 | static inline CList *c_list_init(CList *what) { 58 | *what = (CList)C_LIST_INIT(*what); 59 | return what; 60 | } 61 | 62 | /** 63 | * c_list_entry_offset() - get parent container of list entry 64 | * @what: list entry, or NULL 65 | * @offset: offset of the list member in its surrounding type 66 | * 67 | * If the list entry @what is embedded into a surrounding structure, this will 68 | * turn the list entry pointer @what into a pointer to the parent container 69 | * (sometimes called container_of(3)). Use the `c_list_entry()` macro for an 70 | * easier API. 71 | * 72 | * If @what is NULL, this will also return NULL. 73 | * 74 | * Return: Pointer to parent container, or NULL. 75 | */ 76 | static inline void *c_list_entry_offset(const CList *what, size_t offset) { 77 | if (what) { 78 | /* 79 | * We allow calling "c_list_entry()" on the list head, which is 80 | * commonly a plain CList struct. The returned entry pointer is 81 | * thus invalid. For instance, this is used by the 82 | * c_list_for_each_entry*() macros. Gcc correctly warns about that 83 | * with "-Warray-bounds". However, as long as the value is never 84 | * dereferenced, this is fine. We explicitly use integer arithmetic 85 | * to circumvent the Gcc warning. 86 | */ 87 | return (void *)(((uintptr_t)(void *)what) - offset); 88 | } 89 | return NULL; 90 | } 91 | 92 | /** 93 | * c_list_entry() - get parent container of list entry 94 | * @_what: list entry, or NULL 95 | * @_t: type of parent container 96 | * @_m: member name of list entry in @_t 97 | * 98 | * If the list entry @_what is embedded into a surrounding structure, this will 99 | * turn the list entry pointer @_what into a pointer to the parent container 100 | * (using offsetof(3), or sometimes called container_of(3)). 101 | * 102 | * If @_what is NULL, this will also return NULL. 103 | * 104 | * Return: Pointer to parent container, or NULL. 105 | */ 106 | #define c_list_entry(_what, _t, _m) \ 107 | ((_t *)c_list_entry_offset((_what), offsetof(_t, _m))) 108 | 109 | /** 110 | * c_list_is_linked() - check whether an entry is linked 111 | * @what: entry to check, or NULL 112 | * 113 | * Return: True if @what is linked in a list, false if not. 114 | */ 115 | static inline _Bool c_list_is_linked(const CList *what) { 116 | return what && what->next != what; 117 | } 118 | 119 | /** 120 | * c_list_is_empty() - check whether a list is empty 121 | * @list: list to check, or NULL 122 | * 123 | * This is the same as !c_list_is_linked(). 124 | * 125 | * Return: True if @list is empty, false if not. 126 | */ 127 | static inline _Bool c_list_is_empty(const CList *list) { 128 | return !c_list_is_linked(list); 129 | } 130 | 131 | /** 132 | * c_list_link_before() - link entry into list 133 | * @where: linked list entry used as anchor 134 | * @what: entry to link 135 | * 136 | * This links @what directly in front of @where. @where can either be a list 137 | * head or any entry in the list. 138 | * 139 | * If @where points to the list head, this effectively links @what as new tail 140 | * element. Hence, the macro c_list_link_tail() is an alias to this. 141 | * 142 | * @what is not inspected prior to being linked. Hence, it better not be linked 143 | * into another list, or the other list will be corrupted. 144 | */ 145 | static inline void c_list_link_before(CList *where, CList *what) { 146 | CList *prev = where->prev, *next = where; 147 | 148 | next->prev = what; 149 | what->next = next; 150 | what->prev = prev; 151 | prev->next = what; 152 | } 153 | #define c_list_link_tail(_list, _what) c_list_link_before((_list), (_what)) 154 | 155 | /** 156 | * c_list_link_after() - link entry into list 157 | * @where: linked list entry used as anchor 158 | * @what: entry to link 159 | * 160 | * This links @what directly after @where. @where can either be a list head or 161 | * any entry in the list. 162 | * 163 | * If @where points to the list head, this effectively links @what as new front 164 | * element. Hence, the macro c_list_link_front() is an alias to this. 165 | * 166 | * @what is not inspected prior to being linked. Hence, it better not be linked 167 | * into another list, or the other list will be corrupted. 168 | */ 169 | static inline void c_list_link_after(CList *where, CList *what) { 170 | CList *prev = where, *next = where->next; 171 | 172 | next->prev = what; 173 | what->next = next; 174 | what->prev = prev; 175 | prev->next = what; 176 | } 177 | #define c_list_link_front(_list, _what) c_list_link_after((_list), (_what)) 178 | 179 | /** 180 | * c_list_unlink_stale() - unlink element from list 181 | * @what: element to unlink 182 | * 183 | * This unlinks @what. If @what was initialized via C_LIST_INIT(), it has no 184 | * effect. If @what was never linked, nor initialized, behavior is undefined. 185 | * 186 | * Note that this does not modify @what. It just modifies the previous and next 187 | * elements in the list to no longer reference @what. If you want to make sure 188 | * @what is re-initialized after removal, use c_list_unlink(). 189 | */ 190 | static inline void c_list_unlink_stale(CList *what) { 191 | CList *prev = what->prev, *next = what->next; 192 | 193 | next->prev = prev; 194 | prev->next = next; 195 | } 196 | 197 | /** 198 | * c_list_unlink() - unlink element from list and re-initialize 199 | * @what: element to unlink 200 | * 201 | * This is like c_list_unlink_stale() but re-initializes @what after removal. 202 | */ 203 | static inline void c_list_unlink(CList *what) { 204 | /* condition is not needed, but avoids STOREs in fast-path */ 205 | if (c_list_is_linked(what)) { 206 | c_list_unlink_stale(what); 207 | *what = (CList)C_LIST_INIT(*what); 208 | } 209 | } 210 | 211 | /** 212 | * c_list_swap() - exchange the contents of two lists 213 | * @list1: the list to operate on 214 | * @list2: the list to operate on 215 | * 216 | * This replaces the contents of the list @list1 with the contents 217 | * of @list2, and vice versa. 218 | */ 219 | static inline void c_list_swap(CList *list1, CList *list2) { 220 | CList t; 221 | 222 | /* make neighbors of list1 point to list2, and vice versa */ 223 | t = *list1; 224 | t.next->prev = list2; 225 | t.prev->next = list2; 226 | t = *list2; 227 | t.next->prev = list1; 228 | t.prev->next = list1; 229 | 230 | /* swap list1 and list2 now that their neighbors were fixed up */ 231 | t = *list1; 232 | *list1 = *list2; 233 | *list2 = t; 234 | } 235 | 236 | /** 237 | * c_list_splice() - splice one list into another 238 | * @target: the list to splice into 239 | * @source: the list to splice 240 | * 241 | * This removes all the entries from @source and splice them into @target. 242 | * The order of the two lists is preserved and the source is appended 243 | * to the end of target. 244 | * 245 | * On return, the source list will be empty. 246 | */ 247 | static inline void c_list_splice(CList *target, CList *source) { 248 | if (!c_list_is_empty(source)) { 249 | /* attach the front of @source to the tail of @target */ 250 | source->next->prev = target->prev; 251 | target->prev->next = source->next; 252 | 253 | /* attach the tail of @source to the front of @target */ 254 | source->prev->next = target; 255 | target->prev = source->prev; 256 | 257 | /* clear source */ 258 | *source = (CList)C_LIST_INIT(*source); 259 | } 260 | } 261 | 262 | /** 263 | * c_list_split() - split one list in two 264 | * @source: the list to split 265 | * @where: new starting element of newlist 266 | * @target: new list 267 | * 268 | * This splits @source in two. All elements following @where (including @where) 269 | * are moved to @target, replacing any old list. If @where points to @source 270 | * (i.e., the end of the list), @target will be empty. 271 | */ 272 | static inline void c_list_split(CList *source, CList *where, CList *target) { 273 | if (where == source) { 274 | *target = (CList)C_LIST_INIT(*target); 275 | } else { 276 | target->next = where; 277 | target->prev = source->prev; 278 | 279 | where->prev->next = source; 280 | source->prev = where->prev; 281 | 282 | where->prev = target; 283 | target->prev->next = target; 284 | } 285 | } 286 | 287 | /** 288 | * c_list_first() - return pointer to first element, or NULL if empty 289 | * @list: list to operate on, or NULL 290 | * 291 | * This returns a pointer to the first element, or NULL if empty. This never 292 | * returns a pointer to the list head. 293 | * 294 | * Return: Pointer to first list element, or NULL if empty. 295 | */ 296 | static inline CList *c_list_first(CList *list) { 297 | return c_list_is_empty(list) ? NULL : list->next; 298 | } 299 | 300 | /** 301 | * c_list_last() - return pointer to last element, or NULL if empty 302 | * @list: list to operate on, or NULL 303 | * 304 | * This returns a pointer to the last element, or NULL if empty. This never 305 | * returns a pointer to the list head. 306 | * 307 | * Return: Pointer to last list element, or NULL if empty. 308 | */ 309 | static inline CList *c_list_last(CList *list) { 310 | return c_list_is_empty(list) ? NULL : list->prev; 311 | } 312 | 313 | /** 314 | * c_list_first_entry() - return pointer to first entry, or NULL if empty 315 | * @_list: list to operate on, or NULL 316 | * @_t: type of list entries 317 | * @_m: name of CList member in @_t 318 | * 319 | * This is like c_list_first(), but also applies c_list_entry() on the result. 320 | * 321 | * Return: Pointer to first list entry, or NULL if empty. 322 | */ 323 | #define c_list_first_entry(_list, _t, _m) \ 324 | c_list_entry(c_list_first(_list), _t, _m) 325 | 326 | /** 327 | * c_list_last_entry() - return pointer to last entry, or NULL if empty 328 | * @_list: list to operate on, or NULL 329 | * @_t: type of list entries 330 | * @_m: name of CList member in @_t 331 | * 332 | * This is like c_list_last(), but also applies c_list_entry() on the result. 333 | * 334 | * Return: Pointer to last list entry, or NULL if empty. 335 | */ 336 | #define c_list_last_entry(_list, _t, _m) \ 337 | c_list_entry(c_list_last(_list), _t, _m) 338 | 339 | /** 340 | * c_list_for_each*() - iterators 341 | * 342 | * The c_list_for_each*() macros provide simple for-loop wrappers to iterate 343 | * a linked list. They come in a set of flavours: 344 | * 345 | * - "entry": This combines c_list_entry() with the loop iterator, so the 346 | * iterator always has the type of the surrounding object, rather 347 | * than CList. 348 | * 349 | * - "safe": The loop iterator always keeps track of the next element to 350 | * visit. This means, you can safely modify the current element, 351 | * while retaining loop-integrity. 352 | * You still must not touch any other entry of the list. Otherwise, 353 | * the loop-iterator will be corrupted. 354 | * 355 | * - "continue": Rather than starting the iteration at the front of the list, 356 | * use the current value of the iterator as starting position. 357 | * Note that the first loop iteration will be the following 358 | * element, not the given element. 359 | * 360 | * - "unlink": This unlinks the current element from the list before the loop 361 | * code is run. Note that this only does a partial unlink, since 362 | * it assumes the entire list will be unlinked. You must not 363 | * break out of the loop, or the list will be in an inconsistent 364 | * state. 365 | */ 366 | 367 | /* direct/raw iterators */ 368 | 369 | #define c_list_for_each(_iter, _list) \ 370 | for (_iter = (_list)->next; \ 371 | (_iter) != (_list); \ 372 | _iter = (_iter)->next) 373 | 374 | #define c_list_for_each_safe(_iter, _safe, _list) \ 375 | for (_iter = (_list)->next, _safe = (_iter)->next; \ 376 | (_iter) != (_list); \ 377 | _iter = (_safe), _safe = (_safe)->next) 378 | 379 | #define c_list_for_each_continue(_iter, _list) \ 380 | for (_iter = (_iter) ? (_iter)->next : (_list)->next; \ 381 | (_iter) != (_list); \ 382 | _iter = (_iter)->next) 383 | 384 | #define c_list_for_each_safe_continue(_iter, _safe, _list) \ 385 | for (_iter = (_iter) ? (_iter)->next : (_list)->next, \ 386 | _safe = (_iter)->next; \ 387 | (_iter) != (_list); \ 388 | _iter = (_safe), _safe = (_safe)->next) 389 | 390 | #define c_list_for_each_safe_unlink(_iter, _safe, _list) \ 391 | for (_iter = (_list)->next, _safe = (_iter)->next; \ 392 | c_list_init(_iter) != (_list); \ 393 | _iter = (_safe), _safe = (_safe)->next) 394 | 395 | /* c_list_entry() based iterators */ 396 | 397 | #define c_list_for_each_entry(_iter, _list, _m) \ 398 | for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m); \ 399 | &(_iter)->_m != (_list); \ 400 | _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m)) 401 | 402 | #define c_list_for_each_entry_safe(_iter, _safe, _list, _m) \ 403 | for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \ 404 | _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ 405 | &(_iter)->_m != (_list); \ 406 | _iter = (_safe), \ 407 | _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) 408 | 409 | #define c_list_for_each_entry_continue(_iter, _list, _m) \ 410 | for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next, \ 411 | __typeof__(*_iter), \ 412 | _m); \ 413 | &(_iter)->_m != (_list); \ 414 | _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m)) 415 | 416 | #define c_list_for_each_entry_safe_continue(_iter, _safe, _list, _m) \ 417 | for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next, \ 418 | __typeof__(*_iter), \ 419 | _m), \ 420 | _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ 421 | &(_iter)->_m != (_list); \ 422 | _iter = (_safe), \ 423 | _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) 424 | 425 | #define c_list_for_each_entry_safe_unlink(_iter, _safe, _list, _m) \ 426 | for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \ 427 | _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ 428 | c_list_init(&(_iter)->_m) != (_list); \ 429 | _iter = (_safe), \ 430 | _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) 431 | 432 | /** 433 | * c_list_flush() - flush all entries from a list 434 | * @list: list to flush 435 | * 436 | * This unlinks all entries from the given list @list and reinitializes their 437 | * link-nodes via C_LIST_INIT(). 438 | * 439 | * Note that the entries are not modified in any other way, nor is their memory 440 | * released. This function just unlinks them and resets all the list nodes. It 441 | * is particularly useful with temporary lists on the stack in combination with 442 | * the GCC-extension __attribute__((__cleanup__(arg))). 443 | */ 444 | static inline void c_list_flush(CList *list) { 445 | CList *iter, *safe; 446 | 447 | c_list_for_each_safe_unlink(iter, safe, list) 448 | /* empty */ ; 449 | } 450 | 451 | /** 452 | * c_list_length() - return number of linked entries, excluding the head 453 | * @list: list to operate on 454 | * 455 | * Returns the number of entries in the list, excluding the list head @list. 456 | * That is, for a list that is empty according to c_list_is_empty(), the 457 | * returned length is 0. This requires to iterate the list and has thus O(n) 458 | * runtime. 459 | * 460 | * Note that this function is meant for debugging purposes only. If you need 461 | * the list size during normal operation, you should maintain a counter 462 | * separately. 463 | * 464 | * Return: Number of items in @list. 465 | */ 466 | static inline size_t c_list_length(const CList *list) { 467 | size_t n = 0; 468 | const CList *iter; 469 | 470 | c_list_for_each(iter, list) 471 | ++n; 472 | 473 | return n; 474 | } 475 | 476 | /** 477 | * c_list_contains() - check whether an entry is linked in a certain list 478 | * @list: list to operate on 479 | * @what: entry to look for 480 | * 481 | * This checks whether @what is linked into @list. This requires a linear 482 | * search through the list, as such runs in O(n). Note that the list-head is 483 | * considered part of the list, and hence this returns true if @what equals 484 | * @list. 485 | * 486 | * Note that this function is meant for debugging purposes, and consistency 487 | * checks. You should always be aware whether your objects are linked in a 488 | * specific list. 489 | * 490 | * Return: True if @what is in @list, false otherwise. 491 | */ 492 | static inline _Bool c_list_contains(const CList *list, const CList *what) { 493 | const CList *iter; 494 | 495 | c_list_for_each(iter, list) 496 | if (what == iter) 497 | return 1; 498 | 499 | return what == list; 500 | } 501 | 502 | #ifdef __cplusplus 503 | } 504 | #endif 505 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | # 2 | # target: libclist.so 3 | # (No .so is built so far, since we are header-only. This might change in the 4 | # future, if we add more complex list helpers.) 5 | # 6 | 7 | libclist_dep = declare_dependency( 8 | include_directories: include_directories('.'), 9 | version: meson.project_version(), 10 | ) 11 | 12 | if not meson.is_subproject() 13 | install_headers('c-list.h') 14 | 15 | mod_pkgconfig.generate( 16 | description: project_description, 17 | filebase: 'libclist-'+major, 18 | name: 'libclist', 19 | version: meson.project_version(), 20 | ) 21 | endif 22 | 23 | # 24 | # target: test-* 25 | # 26 | 27 | test_api = executable('test-api', ['test-api.c'], dependencies: libclist_dep) 28 | test('API Symbol Visibility', test_api) 29 | 30 | test_basic = executable('test-basic', ['test-basic.c'], dependencies: libclist_dep) 31 | test('Basic API Behavior', test_basic) 32 | 33 | test_embed = executable('test-embed', ['test-embed.c'], dependencies: libclist_dep) 34 | test('Embedded List Nodes', test_embed) 35 | -------------------------------------------------------------------------------- /src/test-api.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Tests for Public API 3 | * This test, unlikely the others, is linked against the real, distributed, 4 | * shared library. Its sole purpose is to test for symbol availability. 5 | */ 6 | 7 | #undef NDEBUG 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "c-list.h" 13 | 14 | typedef struct { 15 | int id; 16 | CList link; 17 | } Node; 18 | 19 | static void test_api(void) { 20 | CList *list_iter, *list_safe; 21 | CList list = C_LIST_INIT(list), list2 = C_LIST_INIT(list2); 22 | Node node = { .id = 0, .link = C_LIST_INIT(node.link) }; 23 | 24 | assert(c_list_init(&list) == &list); 25 | assert(!c_list_entry_offset(NULL, 0)); 26 | assert(!c_list_entry_offset(NULL, offsetof(Node, link))); 27 | assert(!c_list_entry(NULL, Node, link)); 28 | assert(c_list_entry(&node.link, Node, link) == &node); 29 | assert(!c_list_is_linked(&node.link)); 30 | assert(c_list_is_empty(&list)); 31 | assert(c_list_length(&list) == 0); 32 | assert(c_list_contains(&list, &list)); 33 | assert(!c_list_contains(&list, &node.link)); 34 | c_list_flush(&list); 35 | 36 | /* basic link / unlink calls */ 37 | 38 | c_list_link_before(&list, &node.link); 39 | assert(c_list_is_linked(&node.link)); 40 | assert(!c_list_is_empty(&list)); 41 | assert(c_list_length(&list) == 1); 42 | assert(c_list_contains(&list, &list)); 43 | assert(c_list_contains(&list, &node.link)); 44 | 45 | c_list_unlink_stale(&node.link); 46 | assert(c_list_is_linked(&node.link)); 47 | assert(c_list_is_empty(&list)); 48 | assert(c_list_length(&list) == 0); 49 | 50 | c_list_link_after(&list, &node.link); 51 | assert(c_list_is_linked(&node.link)); 52 | assert(!c_list_is_empty(&list)); 53 | 54 | c_list_unlink(&node.link); 55 | assert(!c_list_is_linked(&node.link)); 56 | assert(c_list_is_empty(&list)); 57 | 58 | /* link / unlink aliases */ 59 | 60 | c_list_link_front(&list, &node.link); 61 | assert(c_list_is_linked(&node.link)); 62 | 63 | c_list_unlink(&node.link); 64 | assert(!c_list_is_linked(&node.link)); 65 | 66 | c_list_link_tail(&list, &node.link); 67 | assert(c_list_is_linked(&node.link)); 68 | 69 | c_list_unlink(&node.link); 70 | assert(!c_list_is_linked(&node.link)); 71 | 72 | /* swap / splice / split list operators */ 73 | 74 | c_list_swap(&list, &list); 75 | assert(c_list_is_empty(&list)); 76 | 77 | c_list_splice(&list, &list); 78 | assert(c_list_is_empty(&list)); 79 | 80 | c_list_split(&list, &list, &list2); 81 | assert(c_list_is_empty(&list)); 82 | assert(c_list_is_empty(&list2)); 83 | 84 | /* direct/raw iterators */ 85 | 86 | c_list_for_each(list_iter, &list) 87 | assert(list_iter != &list); 88 | 89 | c_list_for_each_safe(list_iter, list_safe, &list) 90 | assert(list_iter != &list); 91 | 92 | list_iter = NULL; 93 | c_list_for_each_continue(list_iter, &list) 94 | assert(list_iter != &list); 95 | 96 | list_iter = NULL; 97 | c_list_for_each_safe_continue(list_iter, list_safe, &list) 98 | assert(list_iter != &list); 99 | 100 | c_list_for_each_safe_unlink(list_iter, list_safe, &list) 101 | assert(list_iter != &list); 102 | 103 | /* list accessors */ 104 | 105 | assert(!c_list_first(&list)); 106 | assert(!c_list_last(&list)); 107 | assert(!c_list_first_entry(&list, Node, link)); 108 | assert(!c_list_last_entry(&list, Node, link)); 109 | } 110 | 111 | #if defined(__GNUC__) || defined(__clang__) 112 | static void test_api_gnu(void) { 113 | CList list = C_LIST_INIT(list); 114 | Node *node_iter, *node_safe; 115 | 116 | /* c_list_entry() based iterators */ 117 | 118 | c_list_for_each_entry(node_iter, &list, link) 119 | assert(&node_iter->link != &list); 120 | 121 | c_list_for_each_entry_safe(node_iter, node_safe, &list, link) 122 | assert(&node_iter->link != &list); 123 | 124 | node_iter = NULL; 125 | c_list_for_each_entry_continue(node_iter, &list, link) 126 | assert(&node_iter->link != &list); 127 | 128 | node_iter = NULL; 129 | c_list_for_each_entry_safe_continue(node_iter, node_safe, &list, link) 130 | assert(&node_iter->link != &list); 131 | 132 | c_list_for_each_entry_safe_unlink(node_iter, node_safe, &list, link) 133 | assert(&node_iter->link != &list); 134 | } 135 | #else 136 | static void test_api_gnu(void) { 137 | } 138 | #endif 139 | 140 | int main(void) { 141 | test_api(); 142 | test_api_gnu(); 143 | return 0; 144 | } 145 | -------------------------------------------------------------------------------- /src/test-basic.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Tests for basic functionality 3 | * This contains basic, deterministic tests for list behavior, API 4 | * functionality, and usage. 5 | */ 6 | 7 | #undef NDEBUG 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "c-list.h" 13 | 14 | static void assert_list_integrity(CList *list) { 15 | CList *iter; 16 | 17 | iter = list; 18 | do { 19 | assert(iter->next->prev == iter); 20 | assert(iter->prev->next == iter); 21 | 22 | iter = iter->next; 23 | } while (iter != list); 24 | } 25 | 26 | static void test_iterators(void) { 27 | CList *iter, *safe, a, b, list = C_LIST_INIT(list); 28 | unsigned int i; 29 | 30 | assert(!c_list_first(&list)); 31 | assert(!c_list_last(&list)); 32 | 33 | /* link @a and verify iterators see just it */ 34 | 35 | c_list_link_tail(&list, &a); 36 | assert(c_list_is_linked(&a)); 37 | assert(c_list_first(&list) == &a); 38 | assert(c_list_last(&list) == &a); 39 | 40 | i = 0; 41 | c_list_for_each(iter, &list) { 42 | assert(iter == &a); 43 | ++i; 44 | } 45 | assert(i == 1); 46 | 47 | i = 0; 48 | iter = NULL; 49 | c_list_for_each_continue(iter, &list) { 50 | assert(iter == &a); 51 | ++i; 52 | } 53 | assert(i == 1); 54 | 55 | i = 0; 56 | iter = &a; 57 | c_list_for_each_continue(iter, &list) 58 | ++i; 59 | assert(i == 0); 60 | 61 | /* link @b as well and verify iterators again */ 62 | 63 | c_list_link_tail(&list, &b); 64 | assert(c_list_is_linked(&a)); 65 | assert(c_list_is_linked(&b)); 66 | 67 | i = 0; 68 | c_list_for_each(iter, &list) { 69 | assert((i == 0 && iter == &a) || 70 | (i == 1 && iter == &b)); 71 | ++i; 72 | } 73 | assert(i == 2); 74 | 75 | i = 0; 76 | iter = NULL; 77 | c_list_for_each_continue(iter, &list) { 78 | assert((i == 0 && iter == &a) || 79 | (i == 1 && iter == &b)); 80 | ++i; 81 | } 82 | assert(i == 2); 83 | 84 | i = 0; 85 | iter = &a; 86 | c_list_for_each_continue(iter, &list) { 87 | assert(iter == &b); 88 | ++i; 89 | } 90 | assert(i == 1); 91 | 92 | i = 0; 93 | iter = &b; 94 | c_list_for_each_continue(iter, &list) 95 | ++i; 96 | assert(i == 0); 97 | 98 | /* verify safe-iterator while removing elements */ 99 | 100 | i = 0; 101 | c_list_for_each_safe(iter, safe, &list) { 102 | assert(iter == &a || iter == &b); 103 | c_list_unlink_stale(iter); 104 | ++i; 105 | } 106 | assert(i == 2); 107 | 108 | assert(c_list_is_empty(&list)); 109 | 110 | /* link both and verify *_unlink() iterators */ 111 | 112 | c_list_link_tail(&list, &a); 113 | c_list_link_tail(&list, &b); 114 | 115 | i = 0; 116 | c_list_for_each_safe_unlink(iter, safe, &list) { 117 | assert(iter == &a || iter == &b); 118 | assert(!c_list_is_linked(iter)); 119 | ++i; 120 | } 121 | assert(i == 2); 122 | 123 | assert(c_list_is_empty(&list)); 124 | } 125 | 126 | static void test_swap(void) { 127 | CList list1 = (CList)C_LIST_INIT(list1); 128 | CList list2 = (CList)C_LIST_INIT(list2); 129 | CList list; 130 | 131 | c_list_swap(&list1, &list2); 132 | 133 | assert(list1.prev == list1.next && list1.prev == &list1); 134 | assert(list2.prev == list2.next && list2.prev == &list2); 135 | 136 | c_list_link_tail(&list1, &list); 137 | 138 | assert(c_list_first(&list1) == &list); 139 | assert(c_list_last(&list1) == &list); 140 | assert(list.next == &list1); 141 | assert(list.prev == &list1); 142 | 143 | c_list_swap(&list1, &list2); 144 | 145 | assert(c_list_first(&list2) == &list); 146 | assert(c_list_last(&list2) == &list); 147 | assert(list.next == &list2); 148 | assert(list.prev == &list2); 149 | 150 | assert(list1.prev == list1.next && list1.prev == &list1); 151 | } 152 | 153 | static void test_splice(void) { 154 | CList target = (CList)C_LIST_INIT(target); 155 | CList source = (CList)C_LIST_INIT(source); 156 | CList e1, e2; 157 | 158 | c_list_link_tail(&source, &e1); 159 | 160 | c_list_splice(&target, &source); 161 | assert(c_list_first(&target) == &e1); 162 | assert(c_list_last(&target) == &e1); 163 | 164 | source = (CList)C_LIST_INIT(source); 165 | 166 | c_list_link_tail(&source, &e2); 167 | 168 | c_list_splice(&target, &source); 169 | assert(c_list_first(&target) == &e1); 170 | assert(c_list_last(&target) == &e2); 171 | } 172 | 173 | static void test_split(void) { 174 | CList e1, e2; 175 | 176 | /* split empty list */ 177 | { 178 | CList source = C_LIST_INIT(source), target; 179 | 180 | c_list_split(&source, &source, &target); 181 | assert(c_list_is_empty(&source)); 182 | assert(c_list_is_empty(&target)); 183 | assert_list_integrity(&source); 184 | assert_list_integrity(&target); 185 | } 186 | 187 | /* split 1-element list excluding the element */ 188 | { 189 | CList source = C_LIST_INIT(source), target; 190 | 191 | c_list_link_tail(&source, &e1); 192 | c_list_split(&source, &source, &target); 193 | assert(!c_list_is_empty(&source)); 194 | assert(c_list_is_empty(&target)); 195 | assert_list_integrity(&source); 196 | assert_list_integrity(&target); 197 | } 198 | 199 | /* split 1-element list including the element */ 200 | { 201 | CList source = C_LIST_INIT(source), target; 202 | 203 | c_list_link_tail(&source, &e1); 204 | c_list_split(&source, &e1, &target); 205 | assert(c_list_is_empty(&source)); 206 | assert(!c_list_is_empty(&target)); 207 | assert_list_integrity(&source); 208 | assert_list_integrity(&target); 209 | } 210 | 211 | /* split 2-element list excluding the elements */ 212 | { 213 | CList source = C_LIST_INIT(source), target; 214 | 215 | c_list_link_tail(&source, &e1); 216 | c_list_link_tail(&source, &e2); 217 | c_list_split(&source, &source, &target); 218 | assert(!c_list_is_empty(&source)); 219 | assert(c_list_is_empty(&target)); 220 | assert_list_integrity(&source); 221 | assert_list_integrity(&target); 222 | } 223 | 224 | /* split 2-element list including one element */ 225 | { 226 | CList source = C_LIST_INIT(source), target; 227 | 228 | c_list_link_tail(&source, &e1); 229 | c_list_link_tail(&source, &e2); 230 | c_list_split(&source, &e2, &target); 231 | assert(!c_list_is_empty(&source)); 232 | assert(!c_list_is_empty(&target)); 233 | assert_list_integrity(&source); 234 | assert_list_integrity(&target); 235 | } 236 | 237 | /* split 2-element list including both elements */ 238 | { 239 | CList source = C_LIST_INIT(source), target; 240 | 241 | c_list_link_tail(&source, &e1); 242 | c_list_link_tail(&source, &e2); 243 | c_list_split(&source, &e1, &target); 244 | assert(c_list_is_empty(&source)); 245 | assert(!c_list_is_empty(&target)); 246 | assert_list_integrity(&source); 247 | assert_list_integrity(&target); 248 | } 249 | } 250 | 251 | 252 | static void test_flush(void) { 253 | CList e1 = C_LIST_INIT(e1), e2 = C_LIST_INIT(e2); 254 | CList list1 = C_LIST_INIT(list1), list2 = C_LIST_INIT(list2); 255 | 256 | c_list_link_tail(&list2, &e1); 257 | c_list_link_tail(&list2, &e2); 258 | 259 | assert(c_list_is_linked(&e1)); 260 | assert(c_list_is_linked(&e2)); 261 | 262 | c_list_flush(&list1); 263 | c_list_flush(&list2); 264 | 265 | assert(!c_list_is_linked(&e1)); 266 | assert(!c_list_is_linked(&e2)); 267 | } 268 | 269 | static void test_macros(void) { 270 | /* Verify `c_list_entry()` evaluates arguments only once. */ 271 | { 272 | struct TestList { 273 | int a; 274 | CList link; 275 | int b; 276 | } list = { .link = C_LIST_INIT(list.link) }; 277 | CList *p[2] = { &list.link, NULL }; 278 | unsigned int i = 0; 279 | 280 | assert(i == 0); 281 | assert(c_list_entry(p[i++], struct TestList, link) == &list); 282 | assert(i == 1); 283 | } 284 | } 285 | 286 | #if defined(__GNUC__) || defined(__clang__) 287 | static void test_gnu(void) { 288 | CList e1 = C_LIST_INIT(e1), e2 = C_LIST_INIT(e2); 289 | 290 | /* Test `c_list_flush()` in combination with cleanup attributes. */ 291 | { 292 | __attribute((cleanup(c_list_flush))) CList list1 = C_LIST_INIT(list1); 293 | __attribute((cleanup(c_list_flush))) CList list2 = C_LIST_INIT(list2); 294 | 295 | c_list_link_tail(&list2, &e1); 296 | c_list_link_tail(&list2, &e2); 297 | 298 | assert(c_list_is_linked(&e1)); 299 | assert(c_list_is_linked(&e2)); 300 | } 301 | 302 | assert(!c_list_is_linked(&e1)); 303 | assert(!c_list_is_linked(&e2)); 304 | } 305 | #else 306 | static void test_gnu(void) { 307 | } 308 | #endif 309 | 310 | int main(void) { 311 | test_iterators(); 312 | test_swap(); 313 | test_splice(); 314 | test_split(); 315 | test_flush(); 316 | test_macros(); 317 | test_gnu(); 318 | return 0; 319 | } 320 | -------------------------------------------------------------------------------- /src/test-embed.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Tests for embedded CList members 3 | */ 4 | 5 | #undef NDEBUG 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "c-list.h" 11 | 12 | typedef struct Entry Entry; 13 | 14 | struct Entry { 15 | short foo; 16 | CList link; 17 | short bar; 18 | }; 19 | 20 | static void test_entry(void) { 21 | CList list = C_LIST_INIT(list); 22 | Entry e1 = { .foo = 1 * 7, .bar = 1 * 11 }; 23 | Entry e2 = { .foo = 2 * 7, .bar = 2 * 11 }; 24 | Entry e3 = { .foo = 3 * 7, .bar = 3 * 11 }; 25 | Entry e4 = { .foo = 4 * 7, .bar = 4 * 11 }; 26 | Entry *e; 27 | CList *iter, *safe; 28 | size_t i; 29 | 30 | /* verify c_list_entry() works as expected (even with NULL) */ 31 | 32 | assert(!c_list_entry(NULL, Entry, link)); 33 | assert(&e1 == c_list_entry(&e1.link, Entry, link)); 34 | 35 | /* verify @list is empty */ 36 | 37 | assert(!c_list_first_entry(&list, Entry, link)); 38 | assert(!c_list_last_entry(&list, Entry, link)); 39 | 40 | /* link 2 entries and verify list state */ 41 | 42 | c_list_link_tail(&list, &e1.link); 43 | c_list_link_tail(&list, &e2.link); 44 | 45 | assert(c_list_first_entry(&list, Entry, link)->foo == 1 * 7); 46 | assert(c_list_first_entry(&list, Entry, link)->bar == 1 * 11); 47 | assert(c_list_last_entry(&list, Entry, link)->foo == 2 * 7); 48 | assert(c_list_last_entry(&list, Entry, link)->bar == 2 * 11); 49 | 50 | i = 0; 51 | c_list_for_each(iter, &list) { 52 | e = c_list_entry(iter, Entry, link); 53 | assert(i != 0 || e == &e1); 54 | assert(i != 1 || e == &e2); 55 | assert(i < 2); 56 | ++i; 57 | } 58 | assert(i == 2); 59 | 60 | /* link 2 more entries */ 61 | 62 | c_list_link_tail(&list, &e3.link); 63 | c_list_link_tail(&list, &e4.link); 64 | 65 | assert(c_list_first_entry(&list, Entry, link)->foo == 1 * 7); 66 | assert(c_list_first_entry(&list, Entry, link)->bar == 1 * 11); 67 | assert(c_list_last_entry(&list, Entry, link)->foo == 4 * 7); 68 | assert(c_list_last_entry(&list, Entry, link)->bar == 4 * 11); 69 | 70 | i = 0; 71 | c_list_for_each(iter, &list) { 72 | e = c_list_entry(iter, Entry, link); 73 | assert(i != 0 || e == &e1); 74 | assert(i != 1 || e == &e2); 75 | assert(i != 2 || e == &e3); 76 | assert(i != 3 || e == &e4); 77 | assert(i < 4); 78 | ++i; 79 | } 80 | assert(i == 4); 81 | 82 | assert(!c_list_is_empty(&list)); 83 | assert(c_list_is_linked(&e1.link)); 84 | assert(c_list_is_linked(&e2.link)); 85 | assert(c_list_is_linked(&e3.link)); 86 | assert(c_list_is_linked(&e4.link)); 87 | 88 | /* remove via safe iterator */ 89 | 90 | i = 0; 91 | c_list_for_each_safe(iter, safe, &list) { 92 | e = c_list_entry(iter, Entry, link); 93 | assert(i != 0 || e == &e1); 94 | assert(i != 1 || e == &e2); 95 | assert(i != 2 || e == &e3); 96 | assert(i != 3 || e == &e4); 97 | assert(i < 4); 98 | ++i; 99 | c_list_unlink(&e->link); 100 | } 101 | assert(i == 4); 102 | 103 | assert(c_list_is_empty(&list)); 104 | assert(!c_list_is_linked(&e1.link)); 105 | assert(!c_list_is_linked(&e2.link)); 106 | assert(!c_list_is_linked(&e3.link)); 107 | assert(!c_list_is_linked(&e4.link)); 108 | } 109 | 110 | #if defined(__GNUC__) || defined(__clang__) 111 | static void test_entry_gnu(void) { 112 | CList list = C_LIST_INIT(list); 113 | Entry e1 = { .foo = 1 * 7, .bar = 1 * 11 }; 114 | Entry e2 = { .foo = 2 * 7, .bar = 2 * 11 }; 115 | Entry e3 = { .foo = 3 * 7, .bar = 3 * 11 }; 116 | Entry e4 = { .foo = 4 * 7, .bar = 4 * 11 }; 117 | Entry *e, *safe; 118 | size_t i; 119 | 120 | /* link entries and verify list state */ 121 | 122 | c_list_link_tail(&list, &e1.link); 123 | c_list_link_tail(&list, &e2.link); 124 | c_list_link_tail(&list, &e3.link); 125 | c_list_link_tail(&list, &e4.link); 126 | 127 | i = 0; 128 | c_list_for_each_entry(e, &list, link) { 129 | assert(i != 0 || e == &e1); 130 | assert(i != 1 || e == &e2); 131 | assert(i != 2 || e == &e3); 132 | assert(i != 3 || e == &e4); 133 | assert(i < 4); 134 | ++i; 135 | } 136 | assert(i == 4); 137 | 138 | assert(!c_list_is_empty(&list)); 139 | assert(c_list_is_linked(&e1.link)); 140 | assert(c_list_is_linked(&e2.link)); 141 | assert(c_list_is_linked(&e3.link)); 142 | assert(c_list_is_linked(&e4.link)); 143 | 144 | /* remove via safe iterator */ 145 | 146 | i = 0; 147 | c_list_for_each_entry_safe(e, safe, &list, link) { 148 | assert(i != 0 || e == &e1); 149 | assert(i != 1 || e == &e2); 150 | assert(i != 2 || e == &e3); 151 | assert(i != 3 || e == &e4); 152 | assert(i < 4); 153 | ++i; 154 | c_list_unlink(&e->link); 155 | } 156 | assert(i == 4); 157 | 158 | assert(c_list_is_empty(&list)); 159 | assert(!c_list_is_linked(&e1.link)); 160 | assert(!c_list_is_linked(&e2.link)); 161 | assert(!c_list_is_linked(&e3.link)); 162 | assert(!c_list_is_linked(&e4.link)); 163 | } 164 | #else 165 | static void test_entry_gnu(void) { 166 | } 167 | #endif 168 | 169 | int main(void) { 170 | test_entry(); 171 | test_entry_gnu(); 172 | return 0; 173 | } 174 | --------------------------------------------------------------------------------