I wrote a generic dynamic array using the preprocessor. My use case for it is a compiler I'm writing that uses dynamic arrays a lot, and my current void pointer implementation is becoming a bit hard to use as I have to remember the type each array stores when using it. I'm wondering if this is a good readable implementation and interface.
I'm aware that using an existing library would most likely be better, but as this is for learning purposes, I want to implement all the data structures I use.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define ARRAY_INITIAL_CAPACITY 8
#define Array(type) type *
// [capacity][used][data]...
#define initArray(array) do { \
size_t *data = malloc(2 * sizeof(size_t) + ARRAY_INITIAL_CAPACITY * sizeof(*(array))); \
data[0] = ARRAY_INITIAL_CAPACITY; \
data[1] = 0; \
(array) = (typeof(*(array)) *)&data[2]; \
} while(0)
#define freeArray(array) free((size_t *)(array)-2)
#define arrayCapacity(array) ((array) ? *((size_t *)(array)-2) : 0lu)
#define arrayUsed(array) ((array) ? *((size_t *)(array)-1) : 0lu)
#define arrayEmpty(array) (arrayUsed(array) == 0)
#define arrayPush(array, value) do { \
if(arrayUsed(array) + 1lu >= arrayCapacity(array)) { \
size_t *data = ((size_t *)(array)-2); \
size_t newCap = arrayCapacity(array) * 2 + 2 * sizeof(size_t); \
data = realloc(data, newCap); \
(array) = (typeof(*(array)) *)&data[2]; \
} \
size_t *used = ((size_t *)(array) - 1); \
(array)[(*used)++] = (value); \
} while(0)
#define arrayPop(array) (assert(arrayUsed(array) > 0), (array)[--*((size_t *)(array) - 1)])
#define arrayGet(array, idx) (assert((idx) < arrayUsed(array)), (array)[(idx)])
// callback type has to be void (*)(T, U)
// where T is the type of the items in the array,
// and U is the type of the user_data argument.
#define arrayMap(array, callback, user_data) do { \
for(size_t i = 0; i < arrayUsed(array); ++i) {\
callback(array[i], (user_data)); \
} \
} while(0)
#define arrayCopy(dest, src) do { \
for(size_t i = 0; i < arrayUsed(src); ++i) { \
arrayPush((dest), (src)[i]); \
} \
} while(0)
Example usage:
static void callback(int item, void *user_data) {
printf("%d\n", item);
}
int main(void) {
Array(int) nums = NULL;
initArray(nums);
arrayPush(nums, 1);
arrayPush(nums, 2);
printf("capacity: %lu, used: %lu\n", arrayCapacity(nums), arrayUsed(nums));
// Iterating
for(int i = 0; i < arrayUsed(nums); ++i) {
printf("%d\n", arrayGet(nums, i));
}
// Iterating in reverse
for(int i = arrayUsed(nums)-1; i >= 0; --i) {
printf("%d\n", nums[i]);
}
Array(int) nums2 = NULL;
initArray(nums2);
arrayCopy(nums2, nums);
while(!arrayEmpty(nums)) {
printf("%d\n", arrayPop(nums));
}
arrayMap(nums2, callback, NULL);
freeArray(nums2);
freeArray(nums);
return 0;
}