add reallocarray() like functions - resolves #468

Thu, 31 Oct 2024 17:53:55 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 31 Oct 2024 17:53:55 +0100
changeset 963
2f601274bbac
parent 962
cd418898af5c
child 964
3860f509fcbe

add reallocarray() like functions - resolves #468

CHANGELOG file | annotate | diff | comparison | revisions
src/Makefile file | annotate | diff | comparison | revisions
src/allocator.c file | annotate | diff | comparison | revisions
src/cx/allocator.h file | annotate | diff | comparison | revisions
tests/test_allocator.c file | annotate | diff | comparison | revisions
--- a/CHANGELOG	Thu Oct 31 14:54:44 2024 +0100
+++ b/CHANGELOG	Thu Oct 31 17:53:55 2024 +0100
@@ -2,6 +2,7 @@
 ------------------------
  * adds properties.h
  * adds tree.h
+ * adds reallocarray() like functions to allocator.h
  * adds cxIterator() to create iterators over raw C arrays
  * adds cx_array_reallocator() and cx_array_default_reallocator
  * adds several new array and list functions
--- a/src/Makefile	Thu Oct 31 14:54:44 2024 +0100
+++ b/src/Makefile	Thu Oct 31 17:53:55 2024 +0100
@@ -66,7 +66,8 @@
 
 FORCE:
 
-$(build_dir)/allocator$(OBJ_EXT): allocator.c cx/allocator.h cx/common.h
+$(build_dir)/allocator$(OBJ_EXT): allocator.c cx/allocator.h cx/common.h \
+ cx/utils.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
--- a/src/allocator.c	Thu Oct 31 14:54:44 2024 +0100
+++ b/src/allocator.c	Thu Oct 31 17:53:55 2024 +0100
@@ -27,6 +27,9 @@
  */
 
 #include "cx/allocator.h"
+#include "cx/utils.h"
+
+#include <errno.h>
 
 __attribute__((__malloc__, __alloc_size__(2)))
 static void *cx_malloc_stdlib(
@@ -89,6 +92,27 @@
     }
 }
 
+#undef cx_reallocatearray
+int cx_reallocatearray(
+        void **mem,
+        size_t nmemb,
+        size_t size
+) {
+    size_t n;
+    if (cx_szmul(nmemb, size, &n)) {
+        errno = ENOMEM;
+        return 1;
+    } else {
+        void *nmem = realloc(*mem, n);
+        if (nmem == NULL) {
+            return 1;
+        } else {
+            *mem = nmem;
+            return 0;
+        }
+    }
+}
+
 // IMPLEMENTATION OF HIGH LEVEL API
 
 void *cxMalloc(
@@ -106,6 +130,22 @@
     return allocator->cl->realloc(allocator->data, mem, n);
 }
 
+void *cxReallocArray(
+        const CxAllocator *allocator,
+        void *mem,
+        size_t nmemb,
+        size_t size
+) {
+    size_t n;
+    if (cx_szmul(nmemb, size, &n)) {
+        errno = ENOMEM;
+        return NULL;
+    } else {
+        return allocator->cl->realloc(allocator->data, mem, n);
+    }
+}
+
+#undef cxReallocate
 int cxReallocate(
         const CxAllocator *allocator,
         void **mem,
@@ -120,6 +160,22 @@
     }
 }
 
+#undef cxReallocateArray
+int cxReallocateArray(
+        const CxAllocator *allocator,
+        void **mem,
+        size_t nmemb,
+        size_t size
+) {
+    void *nmem = cxReallocArray(allocator, *mem, nmemb, size);
+    if (nmem == NULL) {
+        return 1;
+    } else {
+        *mem = nmem;
+        return 0;
+    }
+}
+
 void *cxCalloc(
         const CxAllocator *allocator,
         size_t nelem,
--- a/src/cx/allocator.h	Thu Oct 31 14:54:44 2024 +0100
+++ b/src/cx/allocator.h	Thu Oct 31 17:53:55 2024 +0100
@@ -153,6 +153,27 @@
 /**
  * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
  *
+ * The size is calculated by multiplying \p nemb and \p size.
+ *
+ * \par Error handling
+ * \c errno will be set by realloc() on failure or when the multiplication of
+ * \p nmemb and \p size overflows.
+ *
+ * @param mem pointer to the pointer to allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cx_reallocatearray(
+        void **mem,
+        size_t nmemb,
+        size_t size
+);
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ *
  * \par Error handling
  * \c errno will be set by realloc() on failure.
  *
@@ -163,6 +184,22 @@
 #define cx_reallocate(mem, n) cx_reallocate((void**)(mem), n)
 
 /**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ *
+ * The size is calculated by multiplying \p nemb and \p size.
+ *
+ * \par Error handling
+ * \c errno will be set by realloc() on failure or when the multiplication of
+ * \p nmemb and \p size overflows.
+ *
+ * @param mem pointer to the pointer to allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @return zero on success, non-zero on failure
+ */
+#define cx_reallocatearray(mem, nmemb, size) cx_reallocatearray((void**)(mem), nmemb, size)
+
+/**
  * Allocate \p n bytes of memory.
  *
  * @param allocator the allocator
@@ -170,6 +207,7 @@
  * @return a pointer to the allocated memory
  */
 __attribute__((__malloc__))
+__attribute__((__nonnull__))
 __attribute__((__alloc_size__(2)))
 void *cxMalloc(
         const CxAllocator *allocator,
@@ -189,6 +227,7 @@
  * @return a pointer to the re-allocated memory
  */
 __attribute__((__warn_unused_result__))
+__attribute__((__nonnull__(1)))
 __attribute__((__alloc_size__(3)))
 void *cxRealloc(
         const CxAllocator *allocator,
@@ -197,6 +236,32 @@
 );
 
 /**
+ * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving the memory
+ * was not necessary.
+ *
+ * The size is calculated by multiplying \p nemb and \p size.
+ * If that multiplication overflows, this function returns \c NULL and \c errno will be set.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the previously allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @return a pointer to the re-allocated memory
+ */
+__attribute__((__warn_unused_result__))
+__attribute__((__nonnull__(1)))
+__attribute__((__alloc_size__(3, 4)))
+void *cxReallocArray(
+        const CxAllocator *allocator,
+        void *mem,
+        size_t nmemb,
+        size_t size
+);
+
+/**
  * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
  * This function acts like cxRealloc() using the pointer pointed to by \p mem.
  *
@@ -218,6 +283,65 @@
 );
 
 /**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by \p mem.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * \par Error handling
+ * \c errno will be set, if the underlying realloc function does so.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the pointer to allocated block
+ * @param n the new size in bytes
+ * @return zero on success, non-zero on failure
+ */
+#define cxReallocate(allocator, mem, n) cxReallocate(allocator, (void**)(mem), n)
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * This function acts like cxReallocArray() using the pointer pointed to by \p mem.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * \par Error handling
+ * \c errno will be set, if the underlying realloc function does so or the
+ * multiplication of \p nmemb and \p size overflows.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the pointer to allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cxReallocateArray(
+        const CxAllocator *allocator,
+        void **mem,
+        size_t nmemb,
+        size_t size
+);
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * This function acts like cxReallocArray() using the pointer pointed to by \p mem.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * \par Error handling
+ * \c errno will be set, if the underlying realloc function does so or the
+ * multiplication of \p nmemb and \p size overflows.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the pointer to allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @return zero on success, non-zero on failure
+ */
+#define cxReallocateArray(allocator, mem, nmemb, size) \
+        cxReallocateArray(allocator, (void**) (mem), nmemb, size)
+
+/**
  * Allocate \p nelem elements of \p n bytes each, all initialized to zero.
  *
  * @param allocator the allocator
@@ -226,6 +350,7 @@
  * @return a pointer to the allocated memory
  */
 __attribute__((__malloc__))
+__attribute__((__nonnull__))
 __attribute__((__alloc_size__(2, 3)))
 void *cxCalloc(
         const CxAllocator *allocator,
@@ -241,7 +366,7 @@
  * @param allocator the allocator
  * @param mem a pointer to the block to free
  */
-__attribute__((__nonnull__))
+__attribute__((__nonnull__(1)))
 void cxFree(
         const CxAllocator *allocator,
         void *mem
--- a/tests/test_allocator.c	Thu Oct 31 14:54:44 2024 +0100
+++ b/tests/test_allocator.c	Thu Oct 31 17:53:55 2024 +0100
@@ -28,7 +28,13 @@
 
 #include "cx/test.h"
 
+#if __GNUC__
+// we want to perform overflow tests, but we don't want warnings about them
+#define __alloc_size__(...) // NOLINT(*-reserved-identifier)
+#endif
+
 #include "cx/allocator.h"
+#include <errno.h>
 
 CX_TEST(test_allocator_default) {
     CX_TEST_DO {
@@ -68,6 +74,29 @@
     free(test);
 }
 
+CX_TEST(test_allocator_default_reallocarray) {
+    char *test = calloc(8, 1);
+    memcpy(test, "Test", 5);
+    CX_TEST_DO {
+        test = cxReallocArray(cxDefaultAllocator, test, 16, 2);
+        CX_TEST_ASSERT(test != NULL);
+        CX_TEST_ASSERT(0 == strcmp(test, "Test"));
+    }
+    free(test);
+}
+
+CX_TEST(test_allocator_default_reallocarray_overflow) {
+    char *test = calloc(8, 1);
+    memcpy(test, "Test", 5);
+    CX_TEST_DO {
+        void *fail = cxReallocArray(cxDefaultAllocator, test, SIZE_MAX/2, 4);
+        CX_TEST_ASSERT(errno == ENOMEM);
+        CX_TEST_ASSERT(fail == NULL);
+        CX_TEST_ASSERT(0 == strcmp(test, "Test"));
+    }
+    free(test);
+}
+
 CX_TEST(test_allocator_default_free) {
     void *test = malloc(16);
     CX_TEST_DO {
@@ -78,7 +107,7 @@
 }
 
 CX_TEST(test_allocator_reallocate) {
-    void *test = calloc(8, 1);
+    char *test = calloc(8, 1);
     memcpy(test, "Test", 5);
     CX_TEST_DO {
         int ret = cxReallocate(cxDefaultAllocator, &test, 16);
@@ -101,6 +130,56 @@
     free(test);
 }
 
+CX_TEST(test_allocator_reallocatearray) {
+    char *test = calloc(8, 1);
+    memcpy(test, "Test", 5);
+    CX_TEST_DO {
+        int ret = cxReallocateArray(cxDefaultAllocator, &test, 16, 2);
+        CX_TEST_ASSERT(ret == 0);
+        CX_TEST_ASSERT(test != NULL);
+        CX_TEST_ASSERT(0 == strcmp(test, "Test"));
+    }
+    free(test);
+}
+
+CX_TEST(test_allocator_reallocatearray_overflow) {
+    char *test = calloc(8, 1);
+    memcpy(test, "Test", 5);
+    CX_TEST_DO {
+        int ret = cxReallocateArray(cxDefaultAllocator, &test, SIZE_MAX/2, 4);
+        CX_TEST_ASSERT(ret != 0);
+        CX_TEST_ASSERT(errno == ENOMEM);
+        CX_TEST_ASSERT(test != NULL);
+        CX_TEST_ASSERT(0 == strcmp(test, "Test"));
+    }
+    free(test);
+}
+
+CX_TEST(test_allocator_reallocatearray_low_level) {
+    char *test = calloc(8, 1);
+    memcpy(test, "Test", 5);
+    CX_TEST_DO {
+        int ret = cx_reallocatearray(&test, 16, 2);
+        CX_TEST_ASSERT(ret == 0);
+        CX_TEST_ASSERT(test != NULL);
+        CX_TEST_ASSERT(0 == strcmp(test, "Test"));
+    }
+    free(test);
+}
+
+CX_TEST(test_allocator_reallocatearray_low_level_overflow) {
+    char *test = calloc(8, 1);
+    memcpy(test, "Test", 5);
+    CX_TEST_DO {
+        int ret = cx_reallocatearray(&test, SIZE_MAX/2, 4);
+        CX_TEST_ASSERT(ret != 0);
+        CX_TEST_ASSERT(errno == ENOMEM);
+        CX_TEST_ASSERT(test != NULL);
+        CX_TEST_ASSERT(0 == strcmp(test, "Test"));
+    }
+    free(test);
+}
+
 static void *test_allocator_mock_failing_realloc(
         __attribute__((__unused__))void *p,
         __attribute__((__unused__))void *d,
@@ -136,10 +215,16 @@
     cx_test_register(suite, test_allocator_default_malloc);
     cx_test_register(suite, test_allocator_default_calloc);
     cx_test_register(suite, test_allocator_default_realloc);
+    cx_test_register(suite, test_allocator_default_reallocarray);
+    cx_test_register(suite, test_allocator_default_reallocarray_overflow);
     cx_test_register(suite, test_allocator_default_free);
     cx_test_register(suite, test_allocator_reallocate);
     cx_test_register(suite, test_allocator_reallocate_fails);
     cx_test_register(suite, test_allocator_reallocate_low_level);
+    cx_test_register(suite, test_allocator_reallocatearray);
+    cx_test_register(suite, test_allocator_reallocatearray_overflow);
+    cx_test_register(suite, test_allocator_reallocatearray_low_level);
+    cx_test_register(suite, test_allocator_reallocatearray_low_level_overflow);
 
     return suite;
 }

mercurial