4
\$\begingroup\$

The code below is an attempt to mimic std::vector from the C++ standard library in the C language.

The implementation is purely macros, in the C99 dialect, and requires the non-standard __typeof__ keyword.

I am interested to learn of both coding mistakes and design mistakes.

The project resides on SourceForge, in case example code is needed for the review process.

//
// ctnr - Containers for the C language
//
// Written in 2016 by Andrei Bondor, [email protected]
//
// To the extent possible under law, the author(s) have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication along
// with this software (as file "copying.txt"). If not, see:
//
//  http://creativecommons.org/publicdomain/zero/1.0/
//

///
/// @file
/// @brief Implements the Dynamic Array data structure.
/// @details
///
///     Memory management:
///     ------------------
///     DYNARRAY_MALLOC
///     DYNARRAY_REALLOC
///     DYNARRAY_FREE
///
///     General functions:
///     ------------------
///     DynArrayType    dynarray            (ElemType)
///     bool            dynarray_create     (DynArray, Capacity)
///     void            dynarray_ptrcopy    (DynArray, ElemPtr, Count)
///     void            dynarray_destroy    (DynArray)
///     void            dynarray_clear      (DynArray)
///     size_t          dynarray_size       (DynArray)
///     void            dynarray_search     (DynArray, ValidFunc, DynArrayRes)
///     void            dynarray_count      (DynArray, ElemVal, CountRes)
///     bool            dynarray_empty      (DynArray)
///
///     Iterator support:
///     -----------------
///     ElemIterType    dynarrayiter        (DynArray)
///     ElemVal         dynarrayiter_deref  (ElemIter)
///     void            dynarrayiter_inc    (ElemIter)
///     ElemIter        dynarrayiter_begin  (DynArray)
///     ElemIter        dynarrayiter_end    (DynArray)
///
///     Container-specific functions:
///     -----------------------------
///     size_t          dynarray_capacity   (DynArray)
///     void            dynarray_reserve    (DynArray, Capacity)
///     void            dynarray_resize     (DynArray, Count)
///     void            dynarray_resizefill (DynArray, Count, ElemVal)
///     void            dynarray_pushfront  (DynArray, ElemVal)
///     void            dynarray_pushback   (DynArray, ElemVal)
///     void            dynarray_insert     (DynArray, Index, ElemVal)
///     void            dynarray_remove     (DynArray, Index)
///     void            dynarray_fill       (DynArray, ElemVal)
///     void            dynarray_popfront   (DynArray)
///     void            dynarray_popback    (DynArray)
///     ElemPtr         dynarray_ptrdata    (DynArray)
///     ElemVal         dynarray_front      (DynArray)
///     ElemVal         dynarray_back       (DynArray)
///     ElemVal         dynarray_at         (DynArray, Index)
///
///     Debugging support:
///     ------------------
///     void            dynarray_print      (DynArray, Format, Stream)
///

#pragma once

#define VERSION_DYNARRAY            1000

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifndef DYNARRAY_MALLOC
#define DYNARRAY_MALLOC             malloc
#endif

#ifndef DYNARRAY_REALLOC
#define DYNARRAY_REALLOC            realloc
#endif

#ifndef DYNARRAY_FREE
#define DYNARRAY_FREE               free
#endif

///
/// @brief Retrieves the type of the Dynamic Array containing `ElemType`.
/// @details This macro is needed for declaring the Dynamic Array objects.
///
#define dynarray(ElemType)                                                     \
    struct {                                                                   \
        ElemType                   *data;                                      \
        size_t                      count;                                     \
        size_t                      capacity;                                  \
    }

///
/// @brief Creates a new Dynamic Array with a capacity of `Capacity` elements.
/// @param [out] DynArray           The Dynamic Array.
/// @param [in] Capacity            The capacity of the Dynamic Array.
/// @returns Whether or not the operation was successful.
/// @retval true                    OK.
/// @retval false                   Failure (most likely due to memory
///                                 allocation).
///
#define dynarray_create(DynArray, Capacity) (                                  \
    (DynArray).data = DYNARRAY_MALLOC((Capacity) * (sizeof *(DynArray).data)), \
    (DynArray).count = 0,                                                      \
    (DynArray).capacity = (DynArray).data != NULL ? (Capacity) : 0,            \
    (DynArray).data != NULL                                                    \
)

///
/// @brief Copies `Count` elements from `ElemPtr` to the Dynamic Array.
/// @warning The old contents of the Dynamic Array will be lost.
/// @remarks If needed memory cannot be allocated, the contents of the
///  Dynamic Array will not be changed.
/// @param [out] DynArray           The Dynamic Array.
/// @param [in] ElemPtr             Pointer to the elements array.
/// @param [in] Count               Number of elements to be copied.
///
#define dynarray_ptrcopy(DynArray, ElemPtr, Count) if (true) {                 \
    void *ptrdata = DYNARRAY_REALLOC(                                          \
        (DynArray).data,                                                       \
        (sizeof *(DynArray).data) * (Count));                                  \
    if (ptrdata != NULL) {                                                     \
        (DynArray).data = ptrdata;                                             \
        (DynArray).count = (Count);                                            \
        (DynArray).capacity = (Count);                                         \
        memcpy(                                                                \
            (DynArray).data,                                                   \
            (ElemPtr),                                                         \
            (sizeof *(DynArray).data) * (Count));                              \
    }                                                                          \
} else (void)0

///
/// @brief Destroys the Dynamic Array.
/// @param [out] DynArray           The Dynamic Array.
///
#define dynarray_destroy(DynArray) if (true) {                                 \
    DYNARRAY_FREE((DynArray).data);                                            \
    (DynArray).count = 0;                                                      \
    (DynArray).capacity = 0;                                                   \
} else (void)0

///
/// @brief Clears the data from the Dynamic Array.
/// @note Actually it just sets the element count to 0.
/// @param [in,out] DynArray        The Dynamic Array.
///
#define dynarray_clear(DynArray) if (true) {                                   \
    (DynArray).count = 0;                                                      \
} else (void)0

///
/// @brief Returns the number of elements in the Dynamic Array.
/// @param [in] DynArray            The Dynamic Array.
/// @returns `DynArray.count`.
///
#define dynarray_size(DynArray) (                                              \
    (DynArray).count                                                           \
)

///
/// @brief Searches the Dynamic Array for elements that satisfy `ValidFunc`
///  and stores the results in `DynArrayRes`.
/// @param [in] DynArray            The Dynamic Array to be searched.
/// @param [in] ValidFunc           Function or macro to check which elements
///                                 are valid.
/// @param [out] DynArrayRes        The Dynamic Array to store the search
///                                 results.
/// @note `ValidFunc` will be used as if it had the signature:
///  `bool ValidFunc(ElemType)`
/// @note `DynArrayRes` is a Dynamic Array created and destroyed by the caller.
///  Its old contents will be cleared by this function.
///
#define dynarray_search(DynArray, ValidFunc, DynArrayRes) if (true) {          \
    dynarray_clear(DynArrayRes);                                               \
    for (size_t i=0; i < (DynArray).count; ++i) {                              \
        if (ValidFunc((DynArray).data[i])) {                                   \
            dynarray_pushback((DynArrayRes), (DynArray).data[i]);              \
        }                                                                      \
    }                                                                          \
} else (void)0

///
/// @brief Searches the Dynamic Array for elements equal to `ElemVal`
///  and stores the total number in `CountRes`.
/// @param [in] DynArray            The Dynamic Array to be searched.
/// @param [in] ElemVal             Element value to search for.
/// @param [out] CountRes           Count of elements.
///
#define dynarray_count(DynArray, ElemVal, CountRes) if (true) {                \
    (CountRes) = 0;                                                            \
    for (size_t i=0; i < (DynArray).count; ++i) {                              \
        if ((DynArray).data[i] == (ElemVal)) {                                 \
            ++(CountRes);                                                      \
        }                                                                      \
    }                                                                          \
} else (void)0

///
/// @brief Returns if the Dynamic Array is empty.
/// @param [in] DynArray            The Dynamic Array.
/// @retval true                    The Dynamic Array is empty.
/// @retval false                   The Dynamic Array has elements.
///
#define dynarray_empty(DynArray) (                                             \
    (DynArray).count == 0                                                      \
)

///
/// @brief Retrieves the iterator type of a Dynamic Array containing `ElemType`.
/// @details This macro is needed for declaring the Dynamic Array iterators.
/// @param [in] DynArray            The Dynamic Array.
///
#define dynarrayiter(DynArray)                                                 \
    __typeof__ ((DynArray).data)

///
/// @brief Dereferences the `ElemIter` iterator to retrieve the element value.
/// @param [in] ElemIter            The Element Iterator.
/// @returns The element data.
///
#define dynarrayiter_deref(ElemIter) (                                         \
    *(ElemIter)                                                                \
)

///
/// @brief Increments the `ElemIter` iterator.
/// @param [in,out] ElemIter        The Element Iterator.
///
#define dynarrayiter_inc(ElemIter) (                                           \
    ++(ElemIter)                                                               \
)

///
/// @brief Returns iterator to the beginning of the Dynamic Array.
/// @param [in] DynArray            The Dynamic Array.
/// @returns Pointer to the first element.
///
#define dynarrayiter_begin(DynArray) (                                         \
    (DynArray).data                                                            \
)

///
/// @brief Returns iterator to the end of the Dynamic Array.
/// @param [in] DynArray            The Dynamic Array.
/// @returns Pointer to the imaginary element after the last one.
///
#define dynarrayiter_end(DynArray) (                                           \
    (DynArray).data + (DynArray).count                                         \
)

///
/// @brief Returns how many elements can be stored in the Dynamic Array.
/// @param [in] DynArray            The Dynamic Array.
/// @returns `DynArray.capacity`.
///
#define dynarray_capacity(DynArray) (                                          \
    (DynArray).capacity                                                        \
)

///
/// @brief Reserves memory in the Dynamic Array.
/// @remarks If needed memory cannot be allocated, the contents of the
///  Dynamic Array will not be changed.
/// @param [in,out] DynArray        The Dynamic Array.
/// @param [in] Capacity            How many elements the Dynamic Array must
///                                 hold.
///
#define dynarray_reserve(DynArray, Capacity) if (true) {                       \
    void *ptrdata = DYNARRAY_REALLOC(                                          \
        (DynArray).data,                                                       \
        (sizeof *(DynArray).data) * (Capacity));                               \
    if (ptrdata != NULL) {                                                     \
        (DynArray).data = ptrdata;                                             \
        (DynArray).count = (Capacity) < (DynArray).count ?                     \
            (Capacity) : (DynArray).count;                                     \
        (DynArray).capacity = (Capacity);                                      \
    }                                                                          \
} else (void)0

///
/// @brief Resizes the Dynamic Array to hold `Count` elements.
/// @remarks If `Count` is the same as the original element count,
///  nothing will be done.
/// @remarks Memory allocation will only occur if `Count` is greater
///  than the original element count.
/// @remarks If needed memory cannot be allocated, the contents of the
///  Dynamic Array will not be changed.
/// @param [in,out] DynArray        The Dynamic Array.
/// @param [in] Count               How many elements the Dynamic Array must
///                                 hold.
///
#define dynarray_resize(DynArray, Count) if (true) {                           \
    if ((DynArray).count > (Count)) {                                          \
        (DynArray).count = (Count);                                            \
    } else                                                                     \
    if ((DynArray).count < (Count)) {                                          \
        void *ptrdata = DYNARRAY_REALLOC(                                      \
            (DynArray).data,                                                   \
            (sizeof *(DynArray).data) * (Count));                              \
        if (ptrdata != NULL) {                                                 \
            (DynArray).data = ptrdata;                                         \
            (DynArray).count = (Count);                                        \
            (DynArray).capacity = (Count);                                     \
        }                                                                      \
    }                                                                          \
} else (void)0

///
/// @brief Resizes the Dynamic Array to hold `Count` elements.
///  New, if any, elements will be assigned `ElemVal`.
/// @remarks If `Count` is the same as the original element count,
///  nothing will be done.
/// @remarks Memory allocation will only occur if `Count` is greater
///  than the original element count.
/// @remarks If needed memory cannot be allocated, the contents of the
///  Dynamic Array will not be changed.
/// @param [in,out] DynArray        The Dynamic Array.
/// @param [in] Count               How many elements the Dynamic Array must
///                                 hold.
/// @param [in] ElemVal             Default value for new elements.
///
#define dynarray_resizefill(DynArray, Count, ElemVal) if (true) {              \
    if ((DynArray).count > (Count)) {                                          \
        (DynArray).count = (Count);                                            \
    } else                                                                     \
    if ((DynArray).count < (Count)) {                                          \
        void *ptrdata = DYNARRAY_REALLOC(                                      \
            (DynArray).data,                                                   \
            (sizeof *(DynArray).data) * (Count));                              \
        if (ptrdata != NULL) {                                                 \
            (DynArray).data = ptrdata;                                         \
            const size_t begin = (DynArray).count;                             \
            const size_t end = (Count);                                        \
            for (size_t i = begin; i < end; ++i) {                             \
                (DynArray).data[i] = (ElemVal);                                \
            }                                                                  \
            (DynArray).count = (Count);                                        \
            (DynArray).capacity = (Count);                                     \
        }                                                                      \
    }                                                                          \
} else (void)0

///
/// @brief Adds a new element to the front of the Dynamic Array.
/// @remarks The capacity will be more than doubled if it is too low.
/// @remarks If needed memory cannot be allocated, the contents of the
///  Dynamic Array will not be changed.
/// @param [in,out] DynArray        The Dynamic Array.
/// @param [in] ElemVal             Value of the element to be added.
///
#define dynarray_pushfront(DynArray, ElemVal) if (true) {                      \
    void *ptrdata = (DynArray).data;                                           \
    size_t ncapacity = (DynArray).capacity;                                    \
    if ((DynArray).count + 1 > (DynArray).capacity) {                          \
        ptrdata = DYNARRAY_REALLOC(                                            \
            (DynArray).data,                                                   \
            (sizeof *(DynArray).data) * ((DynArray).count + 1) * 2);           \
        ncapacity = ((DynArray).count + 1) * 2;                                \
    }                                                                          \
    if (ptrdata != NULL) {                                                     \
        memmove(                                                               \
            (unsigned char *)ptrdata + (sizeof *(DynArray).data),              \
            ptrdata,                                                           \
            (sizeof *(DynArray).data) * (DynArray).count);                     \
        (DynArray).data = ptrdata;                                             \
        (DynArray).data[0] = (ElemVal);                                        \
        (DynArray).count += 1;                                                 \
        (DynArray).capacity = ncapacity;                                       \
    }                                                                          \
} else (void)0

///
/// @brief Adds a new element to the back of the Dynamic Array.
/// @remarks The capacity will be more than doubled if it is too low.
/// @remarks If needed memory cannot be allocated, the contents of the
///  Dynamic Array will not be changed.
/// @param [in,out] DynArray        The Dynamic Array.
/// @param [in] ElemVal             Value of the element to be added.
///
#define dynarray_pushback(DynArray, ElemVal) if (true) {                       \
    void *ptrdata = (DynArray).data;                                           \
    size_t ncapacity = (DynArray).capacity;                                    \
    if ((DynArray).count + 1 > (DynArray).capacity) {                          \
        ptrdata = DYNARRAY_REALLOC(                                            \
            (DynArray).data,                                                   \
            (sizeof *(DynArray).data) * ((DynArray).count + 1) * 2);           \
        ncapacity = ((DynArray).count + 1) * 2;                                \
    }                                                                          \
    if (ptrdata != NULL) {                                                     \
        (DynArray).data = ptrdata;                                             \
        (DynArray).data[(DynArray).count] = (ElemVal);                         \
        (DynArray).count += 1;                                                 \
        (DynArray).capacity = ncapacity;                                       \
    }                                                                          \
} else (void)0

///
/// @brief Adds a new element at the index `Index` in the Dynamic Array.
/// @remarks The capacity will be more than doubled if it is too low.
/// @remarks If needed memory cannot be allocated, the contents of the
///  Dynamic Array will not be changed.
/// @param [in,out] DynArray        The Dynamic Array.
/// @param [in] Index               Index of the new element.
/// @param [in] ElemVal             Value of the element to be added.
///
#define dynarray_insert(DynArray, Index, ElemVal) if (true) {                  \
    void *ptrdata = (DynArray).data;                                           \
    size_t ncapacity = (DynArray).capacity;                                    \
    if ((DynArray).count + 1 > (DynArray).capacity) {                          \
        ptrdata = DYNARRAY_REALLOC(                                            \
            (DynArray).data,                                                   \
            (sizeof *(DynArray).data) * ((DynArray).count + 1) * 2);           \
        ncapacity = ((DynArray).count + 1) * 2;                                \
    }                                                                          \
    if (ptrdata != NULL) {                                                     \
        (DynArray).data = ptrdata;                                             \
        memmove(                                                               \
            (DynArray).data + (Index) + 1,                                     \
            (DynArray).data + (Index),                                         \
            (sizeof *(DynArray).data) * ((DynArray).count - (Index)));         \
        (DynArray).data[(Index)] = (ElemVal);                                  \
        (DynArray).count += 1;                                                 \
        (DynArray).capacity = ncapacity;                                       \
    }                                                                          \
} else (void)0

///
/// @brief Removes the element at the index `Index` from the Dynamic Array.
/// @remarks If the Dynamic Array is empty, nothing will be done.
/// @param [in,out] DynArray        The Dynamic Array.
/// @param [in] Index               Index of the element to be removed.
///
#define dynarray_remove(DynArray, Index) if (true) {                           \
    if ((DynArray).count != 0) {                                               \
        memmove(                                                               \
            (DynArray).data + (Index),                                         \
            (DynArray).data + (Index) + 1,                                     \
            (sizeof *(DynArray).data) * ((DynArray).count - (Index) - 1));     \
        (DynArray).count -= 1;                                                 \
    }                                                                          \
} else (void)0

///
/// @brief Fills the Dynamic Array will `ElemVal` values.
/// @warning The old contents of `DynArray` will be overwritten.
/// @param [in,out] DynArray        The Dynamic Array.
/// @param [in] ElemVal             New value for all elements.
///
#define dynarray_fill(DynArray, ElemVal) if (true) {                           \
    for (size_t i=0; i < (DynArray).count; ++i) {                              \
        (DynArray).data[i] = (ElemVal);                                        \
    }                                                                          \
} else (void)0

///
/// @brief Removes the element at the front of the Dynamic Array.
/// @pre There must be at least one element in the Dynamic Array.
/// @param [in,out] DynArray        The Dynamic Array.
///
#define dynarray_popfront(DynArray) if (true) {                                \
    assert((DynArray).count != 0);                                             \
    memmove(                                                                   \
        (DynArray).data,                                                       \
        (DynArray).data + 1,                                                   \
        (sizeof *(DynArray).data) * ((DynArray).count - 1));                   \
    (DynArray).count -= 1;                                                     \
} else (void)0

///
/// @brief Removes the element at the back of the Dynamic Array.
/// @pre There must be at least one element in the Dynamic Array.
/// @param [in,out] DynArray        The Dynamic Array.
///
#define dynarray_popback(DynArray) if (true) {                                 \
    assert((DynArray).count != 0);                                             \
    (DynArray).count -= 1;                                                     \
} else (void)0

///
/// @brief Returns a pointer to the raw data of the Dynamic Array.
/// @warning The returned pointer is invalid if the Dynamic Array contains
///  no elements.
/// @param [in] DynArray            The Dynamic Array.
/// @returns Pointer to the first element.
///
#define dynarray_ptrdata(DynArray) (                                           \
    (DynArray).data                                                            \
)

///
/// @brief Retrieves the element value at the front of the Dynamic Array.
/// @pre There must be at least one element in the Dynamic Array.
/// @note Precondition is not checked so that this macro can be used as a
///  modifiable lvalue.
/// @param [in] DynArray            The Dynamic Array.
/// @returns The first element.
///
#define dynarray_front(DynArray) (                                             \
    (DynArray).data[0]                                                         \
)

///
/// @brief Retrieves the element value at the back of the Dynamic Array.
/// @pre There must be at least one element in the Dynamic Array.
/// @note Precondition is not checked so that this macro can be used as a
///  modifiable lvalue.
/// @param [in] DynArray            The Dynamic Array.
/// @returns The last element.
///
#define dynarray_back(DynArray) (                                              \
    (DynArray).data[(DynArray).count - 1]                                      \
)

///
/// @brief Returns the element value at `Index` in the Dynamic Array.
/// @warning No bounds checking shall be performed.
/// @param [in] DynArray            The Dynamic Array.
/// @param [in] Index               Index of the element to be retrieved.
/// @returns The requested element.
///
#define dynarray_at(DynArray, Index) (                                         \
    (DynArray).data[(Index)]                                                   \
)

///
/// @brief Prints Dynamic Array information and contents to the `Stream` file
///  stream.
/// @param [in] DynArray            The Dynamic Array.
/// @param [in] Format              Format string for printing `ElemType`.
/// @param [in] Stream              File stream to print to.
///
#define dynarray_print(DynArray, Format, Stream) if (true) {                   \
    const size_t maxelem = 10;                                                 \
    fprintf((Stream), "--- DYNAMIC ARRAY PRINT --------------------------\n"); \
    fprintf((Stream), "`" #DynArray "` at %p\n\n", (void *)&(DynArray));       \
    fprintf((Stream), "Size:        %zu\n", (DynArray).count);                 \
    fprintf((Stream), "Capacity:    %zu\n", (DynArray).capacity);              \
    fprintf((Stream), "Data at:     %p\n\n", (void *)(DynArray).data);         \
    fprintf((Stream), "Contents:\n");                                          \
    for (size_t i=0; i < (DynArray).count; ++i) {                              \
        if (i % maxelem == 0) {                                                \
            fprintf((Stream), "    ");                                         \
        }                                                                      \
        fprintf((Stream), Format, (DynArray).data[i]);                         \
        if ((i + 1) % maxelem == 0) {                                          \
            fprintf((Stream), "\n");                                           \
        }                                                                      \
    }                                                                          \
    fprintf((Stream), "\n");                                                   \
    fprintf((Stream), "--------------------------------------------------\n"); \
} else (void)0
\$\endgroup\$
1
  • \$\begingroup\$ Some example code using the macros would help. \$\endgroup\$ Oct 17, 2016 at 17:51

2 Answers 2

3
\$\begingroup\$

General comments

Be aware that there are other C dynamic array implementations available, including at least one other I can think of that is implemented entirely with macros. I am uncertain, however, whether there are any others that attempt to cleave so closely to the C++ std::vector interface. I am by no means convinced that that's a desirable characteristic, but neither am I criticizing the project on that basis.

Macro form

The usual convention for the form of a macro that must provide a code block is to use do / while, not if / else:

#define foo(x) do { /* do something */ } while (0)

(See the Linux Kernel style guide, or this one from CMU, or this one from multiple parties including Bell Labs and UC Berkeley.)

Following convention will make your code easier for others to understand and maintain. It will also avoid various compilers emitting unused-value warnings for uses of your macros, and since some projects are very particular about avoiding warnings, the warnings caused by the present form of your macros could be a deal-breaker.

Multiple evaluation

More importantly, however, most of your macros evaluate one or more of their arguments more than once. This makes them very dangerous to use, and if I were evaluating your macro set for use in a project of mine, that by itself would be enough for me to reject it. As long as you are accepting dependence on __typeof__, however, I think there is a workaround that will work for most of your macros, along these lines:

#define dynarray_ptrcopy(DynArray, ElemPtr, Count) do {                        \
    __typeof__(DynArray) *da = &(DynArray);                                    \
    size_t da_count = (Count);                                                 \
    void *ptrdata = DYNARRAY_REALLOC(                                          \
        da->data,                                                              \
        (sizeof *da->data) * da_count);                                        \
    if (ptrdata != NULL) {                                                     \
        da->data = ptrdata;                                                    \
        da->count = da_count;                                                  \
        da->capacity = da_count;                                               \
        memcpy(                                                                \
            da->data,                                                          \
            (ElemPtr),                                                         \
            (sizeof *da->data) * da_count);                                    \
    }                                                                          \
} while (0)

(Note that typical implementations of __typeof__(), like sizeof(), do not evaluate their operands). That approach will not work for dynarray_create(), however, or any other macro that must expand to an expression, because declarations cannot appear inside expressions in C.

Error handling

Several of your macros afford the opportunity for memory allocation failures. You appear generally to have reasonable handling for those cases, except that there is no mechanism for informing the user that a failure has occurred. A user who delved deeply enough into your implementation could probably figure out per-macro means to check whether each macro succeeded, but the macro set ought to provide a standard mechanism. Perhaps you could provide a member in your dynamic array structures that serves as an error flag, and macros to test and reset it.

(Lack of) memory management

The code does a decent job (modulo my preceding comments) of managing the memory associated with each dynamic array itself, but it has no provision for managing memory associated with array elements. That becomes important when elements are pointers to dynamically-allocated memory. This might be acceptable, and certainly it leaves your code simpler, but it would be well to at least discuss the topic either in the general documentation or in the documentation of macros that can cause elements to be lost.

Specific Comments

Don't rely on pragmas

Other than a handful of standard ones, pragmas are implementation-specific. Do not rely on them in code you want to be portable; specifically, in this case, use standard guard macros to protect against multiple inclusion, not #pragma once.

dynarray_create() is misleadingly named

This macro takes a declared dynamic array structure as an argument. It does not create a dynamic array, as its name suggests; instead, it initializes one. Personally, I would name such a macro dynarray_init().

dynarray_destroy() should set the data pointer to NULL

Since dynarray_destroy() does not, in fact, destroy the dynamic array object itself, it should do its best to leave it in a consistent state. That would involve setting its data pointer to NULL after freeing it. This leaves it in the same state as would a failed allocation by dynarray_create(), but more importantly, it avoids undefined behavior in the event that the dynamic array object continues in use instead of being discarded, for NULL is a valid first argument to realloc(), whereas a pointer to space that has been freed is not.

You might also consider renaming this macro, since its name doesn't quite fit. No especially appropriate choice of name immediately occurs to me, however. The best I've come up with is dynarray_clean(); YMMV.

Surprising behavior of dynarray_reserve()

dynarray_reserve() will shrink the allocated space if the requested reservation is smaller than the array's current capacity. This is not what I would expect based directly on its name, nor is it analogous to the behavior of std::vector::reserve(). Moreover, it is unclear why you need this, in its current form, in addition to dynarray_resize().

Macro overkill

The dynarrayiter_deref() and dynarrayiter_inc() seem a bit overkill to me. Just make it clear (for C types; C++ folks will already know) that the iterator type or each dynamic array is one that is suitable for use with the dereferencing and pre- (and post-)increment operators. While you're at it, there's no need to hide that other standard pointer arithmetic works, too.

dynarray_count() works only for scalars

Because of its reliance on comparing elements to the specified value via the == operator, dynarray_count() is unsuitable for element types other than arithmetic types and pointers, and for pointers it tests pointer equality, which may not be what is expected or wanted. Consider, for example, if the element type is char * -- how does a user count based on the contents of C strings to which the elements point? And it's worse for some other element types (structure types, for example), for which expanding this macro produces invalid code. At the very least you need to document these limitations, but it would be nice to have a version that relies on a comparison function.

Unneeded reallocation in dynarray_resize() and dynarray_resizefill()

These macros reallocate space if the new size is greater than the current size, but they need to do so only if the new size is greater than the current capacity.

Problems with printing

The given dynarray_print() implementation is workable only for certain element types -- mainly C strings and scalar types. It cannot be made to print the members of a structure, for instance, if the element type is a structure type or a pointer to such. If this macro is supposed to provide a general-purpose facility then it probably needs to have a parameter by which an element-printing function is provided.

\$\endgroup\$
2
  • \$\begingroup\$ Please update your answer to suggest better names for ctnr_create() and ctnr_destroy(). Regarding the do-while convention for multiline macros, as I was inspired by the C++ Super-FAQ in my choice of if-else, please link your own sources endorsing do-while as a convention. The "macro overkill" is somewhat justified by consistency with other containers which will use nodes instead of bare pointers to iterate. \$\endgroup\$ Oct 17, 2016 at 21:26
  • \$\begingroup\$ @user7023624, I've added suggested alternative names for the *_create and *_destroy macros. The real source for my comments about convention for multiline macros is decades of experience, but I've linked several style guides that back me up on that. I do not personally find the C++Super-FAQ particularly authoritative, and I'd think you yourself would be hesitant to rely on a document that asserts that "Macros are evil in 4 different ways" for a project such as yours. \$\endgroup\$
    – PellMel
    Oct 18, 2016 at 22:03
3
\$\begingroup\$

In addition to the issues identified by user @PellMel here, I have found two more:

Some macros provide a modifiable lvalue when they shouldn't

Macros such as dynarray_capacity() currently allow the user to modify internal data by assignment:

dynarray_capacity(dai) = 0; // the capacity field is set to zero!

These macros can be fixed with a const cast:

#define dynarray_capacity(DynArray) (                                          \
    (const size_t)(DynArray).capacity                                          \
)

Some macros shadow variables

Macros which define variables in their block may hide the variables in the outer scope (of the user code). As this is permitted by the C99 standard (Section 6.2.1/4) it should only result in a compilation warning at most.

Nonetheless this should be fixed by renaming the variables, possibly by suffixing two underscores, as in:

#define dynarray_resizefill(DynArray, Count, ElemVal) do {                     \
    if ((DynArray).count > (Count)) {                                          \
        (DynArray).count = (Count);                                            \
    } else                                                                     \
    if ((DynArray).count < (Count)) {                                          \
        void *ptrdata__ = DYNARRAY_REALLOC(                                    \
            (DynArray).data,                                                   \
            (sizeof *(DynArray).data) * (Count));                              \
        if (ptrdata__ != NULL) {                                               \
            (DynArray).data = ptrdata__;                                       \
            const size_t begin__ = (DynArray).count;                           \
            const size_t end__ = (Count);                                      \
            for (size_t i__ = begin__; i__ < end__; ++i__) {                   \
                (DynArray).data[i__] = (ElemVal);                              \
            }                                                                  \
            (DynArray).count = (Count);                                        \
            (DynArray).capacity = (Count);                                     \
        }                                                                      \
    }                                                                          \
} while (false)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.