I made my own elastic buffer for personal use, it's simple. Let's start with a test code that shows the use of such a buffer / vector.
void buf_test() { int *ints = NULL; enum { N = 1024 }; for (int i = 0; i < N; i++) { buf_push(ints, i); } assert(buf_len(ints) == N); for (int i = 0; i < buf_len(ints); i++) { assert(ints[i] == i); } }
This shows that ints can point to an array or NULL . It is actually an array, and you can access elements as usual ints[i] . But it stands out dynamically, and somehow we need to store its length and capacity .
typedef struct { size_t len; size_t cap; char buf[0]; } Bufhdr;
Some macros help us access this structure.
#define buf__hdr(b) ((Bufhdr *) ((size_t *) b - 2)) #define buf__len(b) ((b) ? buf__hdr(b)->len : 0) #define buf__cap(b) ((b) ? buf__hdr(b)->cap : 0)
And decide when you need to grow our vector.
#define buf__fits(b, n) (buf__len(b) + (n) <= buf__cap(b)) #define buf__grow(b, n) buf_grow((b), buf__len(b) + (n), sizeof(*(b))) #define buf__fit(b, n) (buf__fits(b, n) ? 0 : ((b) = buf__grow(b, n)))
buf_push and buf_len are actually fancy macros, but the operations are simple. It is common practice to implement certain data structures using macros.
#define buf_len(b) buf__hdr(b)->len #define buf_push(b, x) (buf__fit(b, 1), b[buf_len(b)++] = (x))
Only one function remains:
void *buf_grow(const void *buf, size_t len, size_t elem_size) { size_t new_cap, new_size; Bufhdr *hdr; new_cap = buf__cap(buf) ? 2 * buf__cap(buf) : len; assert(len <= new_cap); new_size = offsetof(Bufhdr, buf) + new_cap * elem_size; if (buf) { hdr = realloc(buf__hdr(buf), new_size); } else { hdr = malloc(new_size); hdr->len = 0; } hdr->cap = new_cap; return hdr->buf; }
You can use a pointer to any type - the buffer automatically allocates the necessary memory based on sizeof(*buf)
You can expand it to your needs, for example
#define buf_reserve(b, n) buf_grow((b), (n), sizeof(*(b)) #define buf_free(b) ((b) ? free(buf__hdr(b)) : 0)