src/array_list.c

Sat, 13 Jan 2024 17:51:42 +0100

author
Mike Becker <universe@uap-core.de>
date
Sat, 13 Jan 2024 17:51:42 +0100
changeset 804
5136f2fc32ec
parent 764
ccbdbd088455
child 807
c8d692131b1e
permissions
-rw-r--r--

add CX_DISABLE_ARRAY_LIST_SWAP_SBO flag

universe@606 1 /*
universe@606 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
universe@606 3 *
universe@606 4 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
universe@606 5 *
universe@606 6 * Redistribution and use in source and binary forms, with or without
universe@606 7 * modification, are permitted provided that the following conditions are met:
universe@606 8 *
universe@606 9 * 1. Redistributions of source code must retain the above copyright
universe@606 10 * notice, this list of conditions and the following disclaimer.
universe@606 11 *
universe@606 12 * 2. Redistributions in binary form must reproduce the above copyright
universe@606 13 * notice, this list of conditions and the following disclaimer in the
universe@606 14 * documentation and/or other materials provided with the distribution.
universe@606 15 *
universe@606 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
universe@606 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
universe@606 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
universe@606 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
universe@606 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
universe@606 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
universe@606 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
universe@606 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
universe@606 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
universe@606 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
universe@606 26 * POSSIBILITY OF SUCH DAMAGE.
universe@606 27 */
universe@606 28
universe@606 29 #include "cx/array_list.h"
universe@763 30 #include "cx/compare.h"
universe@610 31 #include <assert.h>
universe@610 32 #include <string.h>
universe@606 33
universe@628 34 // LOW LEVEL ARRAY LIST FUNCTIONS
universe@607 35
universe@612 36 enum cx_array_copy_result cx_array_copy(
universe@610 37 void **target,
universe@610 38 size_t *size,
universe@610 39 size_t *capacity,
universe@610 40 size_t index,
universe@610 41 void const *src,
universe@610 42 size_t elem_size,
universe@610 43 size_t elem_count,
universe@610 44 struct cx_array_reallocator_s *reallocator
universe@610 45 ) {
universe@628 46 // assert pointers
universe@610 47 assert(target != NULL);
universe@610 48 assert(size != NULL);
universe@610 49 assert(src != NULL);
universe@607 50
universe@628 51 // determine capacity
universe@610 52 size_t cap = capacity == NULL ? *size : *capacity;
universe@610 53
universe@628 54 // check if resize is required
universe@627 55 size_t minsize = index + elem_count;
universe@627 56 size_t newsize = *size < minsize ? minsize : *size;
universe@610 57 bool needrealloc = newsize > cap;
universe@610 58
universe@628 59 // reallocate if possible
universe@610 60 if (needrealloc) {
universe@628 61 // a reallocator and a capacity variable must be available
universe@610 62 if (reallocator == NULL || capacity == NULL) {
universe@610 63 return CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED;
universe@610 64 }
universe@610 65
universe@628 66 // check, if we need to repair the src pointer
universe@611 67 uintptr_t targetaddr = (uintptr_t) *target;
universe@611 68 uintptr_t srcaddr = (uintptr_t) src;
universe@611 69 bool repairsrc = targetaddr <= srcaddr
universe@611 70 && srcaddr < targetaddr + cap * elem_size;
universe@611 71
universe@628 72 // calculate new capacity (next number divisible by 16)
universe@625 73 cap = newsize - (newsize % 16) + 16;
universe@625 74 assert(cap > newsize);
universe@610 75
universe@628 76 // perform reallocation
universe@610 77 void *newmem = reallocator->realloc(
universe@610 78 *target, cap, elem_size, reallocator
universe@610 79 );
universe@610 80 if (newmem == NULL) {
universe@610 81 return CX_ARRAY_COPY_REALLOC_FAILED;
universe@610 82 }
universe@610 83
universe@628 84 // repair src pointer, if necessary
universe@611 85 if (repairsrc) {
universe@611 86 src = ((char *) newmem) + (srcaddr - targetaddr);
universe@611 87 }
universe@611 88
universe@628 89 // store new pointer and capacity
universe@610 90 *target = newmem;
universe@610 91 *capacity = cap;
universe@610 92 }
universe@610 93
universe@628 94 // determine target pointer
universe@610 95 char *start = *target;
universe@610 96 start += index * elem_size;
universe@610 97
universe@628 98 // copy elements and set new size
universe@611 99 memmove(start, src, elem_count * elem_size);
universe@610 100 *size = newsize;
universe@610 101
universe@628 102 // return successfully
universe@610 103 return CX_ARRAY_COPY_SUCCESS;
universe@610 104 }
universe@607 105
universe@643 106 #ifndef CX_ARRAY_SWAP_SBO_SIZE
universe@735 107 #define CX_ARRAY_SWAP_SBO_SIZE 128
universe@643 108 #endif
universe@623 109
universe@804 110 bool CX_DISABLE_ARRAY_LIST_SWAP_SBO = false;
universe@804 111
universe@623 112 void cx_array_swap(
universe@623 113 void *arr,
universe@623 114 size_t elem_size,
universe@623 115 size_t idx1,
universe@623 116 size_t idx2
universe@623 117 ) {
universe@660 118 assert(arr != NULL);
universe@660 119
universe@628 120 // short circuit
universe@623 121 if (idx1 == idx2) return;
universe@623 122
universe@623 123 char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE];
universe@623 124 void *tmp;
universe@623 125
universe@628 126 // decide if we can use the local buffer
universe@804 127 if (elem_size > CX_ARRAY_SWAP_SBO_SIZE || CX_DISABLE_ARRAY_LIST_SWAP_SBO) {
universe@623 128 tmp = malloc(elem_size);
universe@628 129 // we don't want to enforce error handling
universe@623 130 if (tmp == NULL) abort();
universe@623 131 } else {
universe@623 132 tmp = sbo_mem;
universe@623 133 }
universe@623 134
universe@628 135 // calculate memory locations
universe@623 136 char *left = arr, *right = arr;
universe@623 137 left += idx1 * elem_size;
universe@623 138 right += idx2 * elem_size;
universe@623 139
universe@628 140 // three-way swap
universe@623 141 memcpy(tmp, left, elem_size);
universe@623 142 memcpy(left, right, elem_size);
universe@623 143 memcpy(right, tmp, elem_size);
universe@623 144
universe@628 145 // free dynamic memory, if it was needed
universe@623 146 if (tmp != sbo_mem) {
universe@623 147 free(tmp);
universe@623 148 }
universe@623 149 }
universe@623 150
universe@628 151 // HIGH LEVEL ARRAY LIST FUNCTIONS
universe@607 152
universe@607 153 typedef struct {
universe@607 154 struct cx_list_s base;
universe@607 155 void *data;
universe@677 156 size_t capacity;
universe@610 157 struct cx_array_reallocator_s reallocator;
universe@607 158 } cx_array_list;
universe@607 159
universe@610 160 static void *cx_arl_realloc(
universe@610 161 void *array,
universe@610 162 size_t capacity,
universe@610 163 size_t elem_size,
universe@610 164 struct cx_array_reallocator_s *alloc
universe@610 165 ) {
universe@628 166 // retrieve the pointer to the list allocator
universe@610 167 CxAllocator const *al = alloc->ptr1;
universe@610 168
universe@628 169 // use the list allocator to reallocate the memory
universe@610 170 return cxRealloc(al, array, capacity * elem_size);
universe@610 171 }
universe@610 172
universe@607 173 static void cx_arl_destructor(struct cx_list_s *list) {
universe@610 174 cx_array_list *arl = (cx_array_list *) list;
universe@708 175
universe@708 176 char *ptr = arl->data;
universe@708 177
universe@708 178 if (list->simple_destructor) {
universe@708 179 for (size_t i = 0; i < list->size; i++) {
universe@708 180 cx_invoke_simple_destructor(list, ptr);
universe@708 181 ptr += list->item_size;
universe@708 182 }
universe@708 183 }
universe@708 184 if (list->advanced_destructor) {
universe@708 185 for (size_t i = 0; i < list->size; i++) {
universe@708 186 cx_invoke_advanced_destructor(list, ptr);
universe@708 187 ptr += list->item_size;
universe@708 188 }
universe@708 189 }
universe@708 190
universe@607 191 cxFree(list->allocator, arl->data);
universe@708 192 cxFree(list->allocator, list);
universe@607 193 }
universe@607 194
universe@638 195 static size_t cx_arl_insert_array(
universe@629 196 struct cx_list_s *list,
universe@638 197 size_t index,
universe@629 198 void const *array,
universe@629 199 size_t n
universe@629 200 ) {
universe@638 201 // out of bounds and special case check
universe@638 202 if (index > list->size || n == 0) return 0;
universe@638 203
universe@638 204 // get a correctly typed pointer to the list
universe@629 205 cx_array_list *arl = (cx_array_list *) list;
universe@638 206
universe@638 207 // do we need to move some elements?
universe@638 208 if (index < list->size) {
universe@638 209 char const *first_to_move = (char const *) arl->data;
universe@677 210 first_to_move += index * list->item_size;
universe@638 211 size_t elems_to_move = list->size - index;
universe@638 212 size_t start_of_moved = index + n;
universe@638 213
universe@638 214 if (CX_ARRAY_COPY_SUCCESS != cx_array_copy(
universe@638 215 &arl->data,
universe@638 216 &list->size,
universe@677 217 &arl->capacity,
universe@638 218 start_of_moved,
universe@638 219 first_to_move,
universe@677 220 list->item_size,
universe@638 221 elems_to_move,
universe@638 222 &arl->reallocator
universe@638 223 )) {
universe@638 224 // if moving existing elems is unsuccessful, abort
universe@638 225 return 0;
universe@638 226 }
universe@638 227 }
universe@638 228
universe@638 229 // note that if we had to move the elements, the following operation
universe@638 230 // is guaranteed to succeed, because we have the memory already allocated
universe@638 231 // therefore, it is impossible to leave this function with an invalid array
universe@638 232
universe@638 233 // place the new elements
universe@629 234 if (CX_ARRAY_COPY_SUCCESS == cx_array_copy(
universe@629 235 &arl->data,
universe@629 236 &list->size,
universe@677 237 &arl->capacity,
universe@638 238 index,
universe@629 239 array,
universe@677 240 list->item_size,
universe@629 241 n,
universe@629 242 &arl->reallocator
universe@629 243 )) {
universe@629 244 return n;
universe@629 245 } else {
universe@629 246 // array list implementation is "all or nothing"
universe@629 247 return 0;
universe@629 248 }
universe@629 249 }
universe@629 250
universe@641 251 static int cx_arl_insert_element(
universe@641 252 struct cx_list_s *list,
universe@641 253 size_t index,
universe@641 254 void const *element
universe@641 255 ) {
universe@641 256 return 1 != cx_arl_insert_array(list, index, element, 1);
universe@641 257 }
universe@641 258
universe@607 259 static int cx_arl_insert_iter(
universe@630 260 struct cx_mut_iterator_s *iter,
universe@607 261 void const *elem,
universe@607 262 int prepend
universe@607 263 ) {
universe@619 264 struct cx_list_s *list = iter->src_handle;
universe@619 265 if (iter->index < list->size) {
universe@641 266 int result = cx_arl_insert_element(
universe@619 267 list,
universe@619 268 iter->index + 1 - prepend,
universe@641 269 elem
universe@619 270 );
universe@619 271 if (result == 0 && prepend != 0) {
universe@619 272 iter->index++;
universe@677 273 iter->elem_handle = ((char *) iter->elem_handle) + list->item_size;
universe@619 274 }
universe@619 275 return result;
universe@619 276 } else {
universe@641 277 int result = cx_arl_insert_element(list, list->size, elem);
universe@619 278 iter->index = list->size;
universe@619 279 return result;
universe@619 280 }
universe@607 281 }
universe@607 282
universe@607 283 static int cx_arl_remove(
universe@607 284 struct cx_list_s *list,
universe@607 285 size_t index
universe@607 286 ) {
universe@664 287 cx_array_list *arl = (cx_array_list *) list;
universe@664 288
universe@628 289 // out-of-bounds check
universe@613 290 if (index >= list->size) {
universe@613 291 return 1;
universe@613 292 }
universe@613 293
universe@664 294 // content destruction
universe@678 295 cx_invoke_destructor(list, ((char *) arl->data) + index * list->item_size);
universe@664 296
universe@628 297 // short-circuit removal of last element
universe@624 298 if (index == list->size - 1) {
universe@624 299 list->size--;
universe@624 300 return 0;
universe@624 301 }
universe@613 302
universe@628 303 // just move the elements starting at index to the left
universe@613 304 int result = cx_array_copy(
universe@613 305 &arl->data,
universe@613 306 &list->size,
universe@677 307 &arl->capacity,
universe@613 308 index,
universe@677 309 ((char *) arl->data) + (index + 1) * list->item_size,
universe@677 310 list->item_size,
universe@626 311 list->size - index - 1,
universe@613 312 &arl->reallocator
universe@613 313 );
universe@613 314 if (result == 0) {
universe@628 315 // decrease the size
universe@613 316 list->size--;
universe@613 317 }
universe@613 318 return result;
universe@607 319 }
universe@607 320
universe@664 321 static void cx_arl_clear(struct cx_list_s *list) {
universe@664 322 if (list->size == 0) return;
universe@664 323
universe@664 324 cx_array_list *arl = (cx_array_list *) list;
universe@664 325 char *ptr = arl->data;
universe@664 326
universe@677 327 if (list->simple_destructor) {
universe@677 328 for (size_t i = 0; i < list->size; i++) {
universe@677 329 cx_invoke_simple_destructor(list, ptr);
universe@677 330 ptr += list->item_size;
universe@664 331 }
universe@677 332 }
universe@677 333 if (list->advanced_destructor) {
universe@677 334 for (size_t i = 0; i < list->size; i++) {
universe@677 335 cx_invoke_advanced_destructor(list, ptr);
universe@677 336 ptr += list->item_size;
universe@664 337 }
universe@664 338 }
universe@666 339
universe@677 340 memset(arl->data, 0, list->size * list->item_size);
universe@666 341 list->size = 0;
universe@664 342 }
universe@664 343
universe@647 344 static int cx_arl_swap(
universe@647 345 struct cx_list_s *list,
universe@647 346 size_t i,
universe@647 347 size_t j
universe@647 348 ) {
universe@647 349 if (i >= list->size || j >= list->size) return 1;
universe@647 350 cx_array_list *arl = (cx_array_list *) list;
universe@677 351 cx_array_swap(arl->data, list->item_size, i, j);
universe@647 352 return 0;
universe@647 353 }
universe@647 354
universe@610 355 static void *cx_arl_at(
universe@607 356 struct cx_list_s const *list,
universe@607 357 size_t index
universe@607 358 ) {
universe@610 359 if (index < list->size) {
universe@610 360 cx_array_list const *arl = (cx_array_list const *) list;
universe@610 361 char *space = arl->data;
universe@677 362 return space + index * list->item_size;
universe@610 363 } else {
universe@610 364 return NULL;
universe@610 365 }
universe@607 366 }
universe@607 367
universe@764 368 static ssize_t cx_arl_find_remove(
universe@764 369 struct cx_list_s *list,
universe@764 370 void const *elem,
universe@764 371 bool remove
universe@607 372 ) {
universe@660 373 assert(list->cmpfunc != NULL);
universe@699 374 assert(list->size < SIZE_MAX / 2);
universe@614 375 char *cur = ((cx_array_list const *) list)->data;
universe@614 376
universe@699 377 for (ssize_t i = 0; i < (ssize_t) list->size; i++) {
universe@614 378 if (0 == list->cmpfunc(elem, cur)) {
universe@764 379 if (remove) {
universe@764 380 if (0 == cx_arl_remove(list, i)) {
universe@764 381 return i;
universe@764 382 } else {
universe@764 383 return -1;
universe@764 384 }
universe@764 385 } else {
universe@764 386 return i;
universe@764 387 }
universe@614 388 }
universe@677 389 cur += list->item_size;
universe@614 390 }
universe@614 391
universe@699 392 return -1;
universe@607 393 }
universe@607 394
universe@607 395 static void cx_arl_sort(struct cx_list_s *list) {
universe@660 396 assert(list->cmpfunc != NULL);
universe@615 397 qsort(((cx_array_list *) list)->data,
universe@615 398 list->size,
universe@677 399 list->item_size,
universe@615 400 list->cmpfunc
universe@615 401 );
universe@607 402 }
universe@607 403
universe@607 404 static int cx_arl_compare(
universe@607 405 struct cx_list_s const *list,
universe@607 406 struct cx_list_s const *other
universe@607 407 ) {
universe@660 408 assert(list->cmpfunc != NULL);
universe@622 409 if (list->size == other->size) {
universe@622 410 char const *left = ((cx_array_list const *) list)->data;
universe@622 411 char const *right = ((cx_array_list const *) other)->data;
universe@622 412 for (size_t i = 0; i < list->size; i++) {
universe@622 413 int d = list->cmpfunc(left, right);
universe@622 414 if (d != 0) {
universe@622 415 return d;
universe@622 416 }
universe@677 417 left += list->item_size;
universe@677 418 right += other->item_size;
universe@622 419 }
universe@622 420 return 0;
universe@622 421 } else {
universe@622 422 return list->size < other->size ? -1 : 1;
universe@622 423 }
universe@607 424 }
universe@607 425
universe@607 426 static void cx_arl_reverse(struct cx_list_s *list) {
universe@623 427 if (list->size < 2) return;
universe@623 428 void *data = ((cx_array_list const *) list)->data;
universe@623 429 size_t half = list->size / 2;
universe@623 430 for (size_t i = 0; i < half; i++) {
universe@677 431 cx_array_swap(data, list->item_size, i, list->size - 1 - i);
universe@623 432 }
universe@607 433 }
universe@607 434
universe@630 435 static bool cx_arl_iter_valid(void const *it) {
universe@630 436 struct cx_iterator_s const *iter = it;
universe@616 437 struct cx_list_s const *list = iter->src_handle;
universe@616 438 return iter->index < list->size;
universe@616 439 }
universe@616 440
universe@630 441 static void *cx_arl_iter_current(void const *it) {
universe@630 442 struct cx_iterator_s const *iter = it;
universe@616 443 return iter->elem_handle;
universe@616 444 }
universe@616 445
universe@630 446 static void cx_arl_iter_next(void *it) {
universe@630 447 struct cx_iterator_base_s *itbase = it;
universe@630 448 if (itbase->remove) {
universe@630 449 struct cx_mut_iterator_s *iter = it;
universe@630 450 itbase->remove = false;
universe@616 451 cx_arl_remove(iter->src_handle, iter->index);
universe@616 452 } else {
universe@630 453 struct cx_iterator_s *iter = it;
universe@616 454 iter->index++;
universe@620 455 iter->elem_handle =
universe@620 456 ((char *) iter->elem_handle)
universe@677 457 + ((struct cx_list_s const *) iter->src_handle)->item_size;
universe@616 458 }
universe@616 459 }
universe@616 460
universe@655 461 static void cx_arl_iter_prev(void *it) {
universe@655 462 struct cx_iterator_base_s *itbase = it;
universe@655 463 struct cx_mut_iterator_s *iter = it;
universe@655 464 cx_array_list *const list = iter->src_handle;
universe@655 465 if (itbase->remove) {
universe@655 466 itbase->remove = false;
universe@655 467 cx_arl_remove(iter->src_handle, iter->index);
universe@655 468 }
universe@655 469 iter->index--;
universe@655 470 if (iter->index < list->base.size) {
universe@655 471 iter->elem_handle = ((char *) list->data)
universe@677 472 + iter->index * list->base.item_size;
universe@655 473 }
universe@655 474 }
universe@655 475
universe@630 476 static bool cx_arl_iter_flag_rm(void *it) {
universe@630 477 struct cx_iterator_base_s *iter = it;
universe@630 478 if (iter->mutating) {
universe@630 479 iter->remove = true;
universe@630 480 return true;
universe@630 481 } else {
universe@630 482 return false;
universe@630 483 }
universe@630 484 }
universe@630 485
universe@607 486 static struct cx_iterator_s cx_arl_iterator(
universe@630 487 struct cx_list_s const *list,
universe@655 488 size_t index,
universe@655 489 bool backwards
universe@607 490 ) {
universe@607 491 struct cx_iterator_s iter;
universe@607 492
universe@616 493 iter.index = index;
universe@616 494 iter.src_handle = list;
universe@616 495 iter.elem_handle = cx_arl_at(list, index);
universe@630 496 iter.base.valid = cx_arl_iter_valid;
universe@630 497 iter.base.current = cx_arl_iter_current;
universe@655 498 iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
universe@630 499 iter.base.flag_removal = cx_arl_iter_flag_rm;
universe@630 500 iter.base.remove = false;
universe@630 501 iter.base.mutating = false;
universe@616 502
universe@607 503 return iter;
universe@607 504 }
universe@607 505
universe@607 506 static cx_list_class cx_array_list_class = {
universe@607 507 cx_arl_destructor,
universe@641 508 cx_arl_insert_element,
universe@638 509 cx_arl_insert_array,
universe@607 510 cx_arl_insert_iter,
universe@607 511 cx_arl_remove,
universe@664 512 cx_arl_clear,
universe@647 513 cx_arl_swap,
universe@607 514 cx_arl_at,
universe@764 515 cx_arl_find_remove,
universe@607 516 cx_arl_sort,
universe@607 517 cx_arl_compare,
universe@607 518 cx_arl_reverse,
universe@607 519 cx_arl_iterator,
universe@607 520 };
universe@607 521
universe@670 522 CxList *cxArrayListCreate(
universe@606 523 CxAllocator const *allocator,
universe@677 524 cx_compare_func comparator,
universe@606 525 size_t item_size,
universe@606 526 size_t initial_capacity
universe@606 527 ) {
universe@670 528 if (allocator == NULL) {
universe@670 529 allocator = cxDefaultAllocator;
universe@670 530 }
universe@670 531
universe@607 532 cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
universe@607 533 if (list == NULL) return NULL;
universe@607 534
universe@607 535 list->base.cl = &cx_array_list_class;
universe@607 536 list->base.allocator = allocator;
universe@677 537 list->capacity = initial_capacity;
universe@607 538
universe@667 539 if (item_size > 0) {
universe@677 540 list->base.item_size = item_size;
universe@763 541 list->base.cmpfunc = comparator;
universe@667 542 } else {
universe@678 543 item_size = sizeof(void *);
universe@763 544 list->base.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
universe@667 545 cxListStorePointers((CxList *) list);
universe@667 546 }
universe@667 547
universe@676 548 // allocate the array after the real item_size is known
universe@676 549 list->data = cxCalloc(allocator, initial_capacity, item_size);
universe@676 550 if (list->data == NULL) {
universe@676 551 cxFree(allocator, list);
universe@676 552 return NULL;
universe@676 553 }
universe@676 554
universe@628 555 // configure the reallocator
universe@610 556 list->reallocator.realloc = cx_arl_realloc;
universe@610 557 list->reallocator.ptr1 = (void *) allocator;
universe@610 558
universe@607 559 return (CxList *) list;
universe@606 560 }

mercurial