Skip to content

Instantly share code, notes, and snippets.

@tom-seddon
Last active April 29, 2018 22:48
Show Gist options
  • Save tom-seddon/33d95d06a5fe462b633ef5d446390f6d to your computer and use it in GitHub Desktop.
Save tom-seddon/33d95d06a5fe462b633ef5d446390f6d to your computer and use it in GitHub Desktop.
Arrays in C

Growable arrays in C

I don't think there's any objectively good solution to this perennial annoyance, so this is just some random thing that tries to tick the specific boxes I want ticked: simple to look at in the debugger; few/no casts during use; easy interop with existing C arrays; OK performance in an unoptimised build.

How it works

A growable array needs 3 values: base, length and capacity. This system just treats any 3 variables of the right names and types as defining an array. There's a macro that automates the definition of these variables.

#define ARRAY(T,S) T *S; size_t S##_length; size_t S##_capacity

You can use this to create 3 locals, 3 globals, or 3 struct members. Like this:

ARRAY(int, xs);

You'll also need to initialise and these destroy array "objects".

#define ARRAY_INIT(S)       \
    do {                    \
        S = NULL;           \
        (S##_length) = 0;   \
        (S##_capacity) = 0; \
    } while(0)

#define ARRAY_DESTROY(S) \
    do {                 \
        Array_Free(S);   \
        ARRAY_INIT(S);   \
    } while(0)

You'll probably want to add an item to an array too.

#define ARRAY_ADD(S,X)                           \
    do {                                         \
        if((S##_length) >= (S##_capacity)) {     \
            S = Array_Grow(S,                    \
                           sizeof *S,            \
                           &(S##_length),        \
                           &(S##_capacity));     \
        }                                        \
        (S)[(S##_length)++] = (X);               \
    } while(0)

So you might use them like this:

ARRAY(int, xs);
ARRAY_INIT(xs);
for(int i = 0; i < 100; ++i)
    ARRAY_ADD(xs, i);
ARRAY_DESTROY(xs);

Array_Free is very simple, and Array_Grow is barely more complicated (however I wrote it off the cuff, so of course it could still be wrong). Both of these mainly exist just to keep stdlib.h out of the array header.

void Array_Free(void *p) {
    free(p);
}

/* Pick any value you like */
#define MIN_CAPACITY ((size_t)1)

void *Array_Grow(void *base, size_t stride, size_t *length, size_t *capacity) {
    *capacity += *capacity / 2;
    *capacity = MAX(*capacity, MAX(MIN_CAPACITY, *length + 1));
    return realloc(base, *capacity * stride);
}

(Note that if the capacity is smaller than the length, Array_Grow will assume the capacity is the length. This is so you can use malloc'd buffers from other sources, that have a known size but unknown capacity. Set the capacity to 0 and Array_Grow will do something sensible.)

Array accesses and iteration and the like are just done in the traditional way:

for(size_t i = 0; i < xs_length; ++i) {
    printf("%d\n", xs[i]);
}

And so on, and so on - there's plenty more you can do, much of it obvious. But the above is the long and the short of it.

More stuff

Passing arrays as parameters

If a function is just going to consume an array, typically it only needs the base and length, so those can just be passed in manually.

If the function is going to modify the array, it'll need all 3 variables. More macros for this:

#define ARRAY_PARAMS(T,S) T *S, size_t S##_length, size_t S##_capacity
#define ARRAY_ARG(S) S, S##_length, S##_capacity

Then you might have a function that takes a pointer to an "array":

void FunctionThatTakesAnArray(ARRAY_PARAMS(int, *p));

And you call it like this:

ARRAY(int, myarray);
FunctionThatTakesAnArray(ARRAY_ARG(&myarray));

And operate on it like this:

void FunctionThatTakesAnArray(ARRAY_PARAMS(int, *p)) {
    ARRAY_ADD(*p, 100);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment