Zero: Dynamic Arrays

Dynamic arrays stand for arrays that can grow and shrink in size. They are conceptualized in C++ through the std::vector class, but no such facility is available out of the box in C, leading to countless different implementations by countless different developers. This is my contribution to it!

A trendy, minimalist, implementation comes from Sean Barrett’s stretchy buffer but I decided to go for a slightly more involved approach.

ZeroView on GitHub

Inspired by C++ templates, the library provides a ZR_MAKE_DYNAMIC_ARRAY() macro that generates all the functions needed to manipulate a dynamic array for a given type. The library’s code is slightly abominable but it’s pretty neat on the user’s end, with all the type checking you’d get with the usual function signatures.

One cool feature, as shared by Zero’s allocator library and Sean Barrett’s stretchy buffers, is that these arrays are not wrapped into any sort of structure to make them work—all the bookkeeping required is instead stored in a header invisible to the user, so you can still pass your arrays around as if they were plain C arrays.

For the manipulation side of things, three main families of functions provide facilities to add new elements into an array:

  • extend: adds new uninitialized element(s) at a given position and returns a pointer to the first element added
  • insert: adds new element(s) at a given position and initializes them by copying the given input values
  • push: adds a new element at a given position and initializes it by copying the given input value

And a single one is dedicated to removing elements: trim.

Each of these 4 functions come in different flavours. While the base one offers an explicit control over where the modification needs to occur, two variants exist as a shortcut to focus on the front and back ends of the arrays.

Here’s a usage example, with error checking omitted for brevity:

#define ZR_DEFINE_IMPLEMENTATION
#include <zero/dynamicarray.h>

ZR_MAKE_DYNAMIC_ARRAY(IntArray, int)

int
main(void)
{
    int *pArray;
    int *pSlice;
    int values[2];

    zrCreateIntArray(&pArray, 2);
    pArray[0] = 1;
    pArray[1] = 2;
    /* pArray: {1, 2} */

    zrResizeIntArray(&pArray, 4);
    pArray[2] = 7;
    pArray[3] = 8;
    /* pArray: {1, 2, 7, 8} */

    zrExtendIntArray(&pSlice, &pArray, 2, 2);
    pSlice[0] = 3;
    pSlice[1] = 6;
    /* pArray: {1, 2, 3, 6, 7, 8} */

    values[0] = 4;
    values[1] = 5;
    zrInsertIntArray(&pArray, 3, sizeof values / sizeof values[0], values);
    /* pArray: {1, 2, 3, 4, 5, 6, 7, 8} */

    zrPushIntArrayFront(&pArray, 0);
    zrPushIntArrayBack(&pArray, 9);
    /* pArray: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} */

    zrDestroyIntArray(pArray);

    return 0;
}

I’m not sure what the drawbacks of this approach are (if any) but that’s exactly why I decided to experiment with it!

If you have some feedback, please do share!

The implementation is available on GitHub.