Wrote this (and others not here) as an exercise to explore how generics could be implemented in C.
Question -- if I were to support out-of-band error setting, how would that look like for dynarray_len()
? There is no wrong value to return and requiring the user to check the error after every call seems cumbersome; I don't see any other way around though.
// generic_dynarray.h #ifndef GENERIC_DYNARRAY_H #define GENERIC_DYNARRAY_H #include <stdbool.h> typedef struct dynarray_T dynarray_T; // dynarray_new returns a new, initialised dynarray_T. dynarray_T *dynarray_new(size_t data_size); // dynarray_destroy frees da. void dynarray_destroy(dynarray_T *da); // dynarray_init initialises or clears da. void dynarray_init(dynarray_T *da, size_t data_size); // dynarray_get returns the data at index i of da, otherwise NULL incase // of out-of-bounds i. void *dynarray_get(dynarray_T *da, size_t i); // dynarray_set sets the value of index i of da to data. Returns true if // operation succeeded, false otherwise. bool dynarray_set(dynarray_T *da, size_t i, void *data); // dynarray_append appends data to da. void dynarray_append(dynarray_T *da, void *data); // dynarray_pop pops and returns the value of da at it's last index. void *dynarray_pop(dynarray_T *da); // dynarray_len returns the length of da. size_t dynarray_len(dynarray_T *da); #endif
// generic_dynarray.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include "generic_dynarray.h" #define DYNARRAY_MIN_CAP 10 #define DYNARRAY_CAP_GROWTH_RATE 2 #define DYNARRAY_CAP_SHRINK_RATE 0.25 // 1/4 struct dynarray_T { void *buf; // The buffer array holding the data size_t cap, len; // The capacity and length of the dynarray size_t data_size; // The size of the value stored in buf }; static void valid_dynarray(dynarray_T *da, const char *func) { if (!da) { fprintf(stderr, "%s: dynarray_T should not be NULL", func); exit(1); } } dynarray_T *dynarray_new(size_t data_size) { dynarray_T *da = malloc(sizeof(*da)); if (!da) { fprintf(stderr, "%s: memory allocation for da failed\n", __func__); exit(1); } // For realloc in dynarray_init() to work like malloc da->buf = NULL; dynarray_init(da, data_size); return da; } void dynarray_destroy(dynarray_T *da) { valid_dynarray(da, __func__); free(da->buf); free(da); } // dynarray_resize resizes da->buf to capacity da->cap by reallocing. static void dynarray_resize(dynarray_T *da, const char *func) { da->buf = realloc(da->buf, da->data_size * da->cap); if (!da->buf) { fprintf(stderr, "%s: memory allocation for da->buf failed\n", func); exit(1); } } void dynarray_init(dynarray_T *da, size_t data_size) { valid_dynarray(da, __func__); da->cap = DYNARRAY_MIN_CAP; da->len = 0; da->data_size = data_size; dynarray_resize(da, __func__); } void *dynarray_get(dynarray_T *da, size_t i) { valid_dynarray(da, __func__); if (i >= da->len) { fprintf(stderr, "%s: i %lu must be less than %lu\n", __func__, i, da->len); return NULL; } return da->buf + i*da->data_size; } bool dynarray_set(dynarray_T *da, size_t i, void *data) { valid_dynarray(da, __func__); if (i >= da->len) { fprintf(stderr, "%s: i %lu must be less than %lu\n", __func__, i, da->len); return false; } memcpy(da->buf + i*da->data_size, data, da->data_size); return true; } void dynarray_append(dynarray_T *da, void *data) { valid_dynarray(da, __func__); if (da->cap == da->len+1) { da->cap = DYNARRAY_CAP_GROWTH_RATE * da->cap; dynarray_resize(da, __func__); } dynarray_set(da, da->len++, data); } void *dynarray_pop(dynarray_T *da) { valid_dynarray(da, __func__); if (da->len == DYNARRAY_CAP_SHRINK_RATE * da->cap) { da->cap = DYNARRAY_CAP_SHRINK_RATE * da->cap; dynarray_resize(da, __func__); } void *ret = dynarray_get(da, da->len-1); da->len--; return ret; } size_t dynarray_len(dynarray_T *da) { valid_dynarray(da, __func__); return da->len; }
And a simple test driver,
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "generic_dynarray.h" int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "expect at least one argument\n"); exit(1); } dynarray_T *da_strs = dynarray_new(sizeof(char *)); for (size_t i = 0; i < argc; i++) { dynarray_append(da_strs, argv[i]); } printf("da.len = %lu\n", dynarray_len(da_strs)); for (size_t i = 0; i < dynarray_len(da_strs); i++) { printf("da_strs[%lu] = %s; ", i, (char *)dynarray_get(da_strs, i)); } puts(""); size_t len = dynarray_len(da_strs); for (size_t i = 0; i < len; i++) { printf("da_strs[%lu] = %s; ", len-i-1, (char *)dynarray_pop(da_strs)); printf("da.len = %lu\n", dynarray_len(da_strs)); } dynarray_destroy(da_strs); return 0; }