src/array_list.c

Sun, 21 May 2023 14:56:10 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 21 May 2023 14:56:10 +0200
changeset 708
1caed6c9ba68
parent 699
35b2b99ee523
child 735
b686d0c98c62
permissions
-rw-r--r--

fix inconsistent destructor requirements for list and map classes

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

mercurial