by tom_ | 2025-07-26 21:11:33+00:00
What is an array? It's 3 variables: base, length and capacity. So why not decide that an array is just that. 3 variables of the right size and type.

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

Then you can make one 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)

Add 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 header.

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

Array accesses and iteration and the like are just done in the traditional way. Even performs nicely with -O0.

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

You might want to hide the size naming policy behind a macro:

#define ARRAY_LENGTH(S) (S##_length)

But it'd often be shorter just to type the name out. (On the other hand, if attempting to fully productize this, maybe it'd be nice to let the consumer customize the naming convention - and now you'd have the option. ARRAY_LENGTH would hide the details, you'd fix up ARRAY_ADD (etc.) to use it, and now you could have the size variable called arraySize (or whatever) and everything would fall into line.)

For a full implementation you'll probably also need a way of generating a static array. (I mainly found myself needing this for test code, which uses globals for convenience; most arrays I create normally are locals, or parts of structs.)

You'll also need a parameters list for use in a function declaration or definition, and a macro that expands to all 3 variables.

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

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

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

And you call it like this:

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

(I found this cropped up often enough that I needed the macro, but it was less common than I thought.)

There's more you can do, but the above is the long and the short of it.

This might all look terrible - or perhaps it sort of looks OK, but you're just not sure that it would actually work - but I've used this in a prototype project and thought it worked out well. (I mainly do C++ nowadays, but I had a good stint of C before that. So even if I've got no taste, hopefully I've at least got a rough feel for what'll work out OK and what'll end up a disaster.)