Last active
May 16, 2026 05:37
-
-
Save cartercanedy/deb8cbd9c6e1c46b96a4d83976791514 to your computer and use it in GitHub Desktop.
A lightweight, header-only dynamically-sized array implmentation with first-class support for arbitrary types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef INLINE_VEC_H | |
| #define INLINE_VEC_H | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <stdint.h> | |
| #if defined(__cplusplus) && __cplusplus >= 201703L | |
| #define VEC_DECL [[maybe_unused]] static inline | |
| #elif defined(__GNUC__) || defined(__clang__) | |
| #define VEC_DECL __attribute__((unused)) static inline | |
| #else | |
| #define VEC_DECL static inline | |
| #endif | |
| const static size_t VEC_INIT_CAPACITY = 8; | |
| const static size_t VEC_MAGIC_NUMBER = 0xDEADBEEF; | |
| #ifndef VEC_UNCHECKED | |
| # define VEC_ASSERT(vec, item_size) \ | |
| if ((vec)->item_size != (item_size) || item_size == 0) { \ | |
| return VEC_RESULT_ERR_UNINITIALIZED; \ | |
| } else if ((vec)->magic_number != VEC_MAGIC_NUMBER) { \ | |
| return VEC_RESULT_ERR_SIZEMISMATCH; \ | |
| } else if ((vec)->size > (vec)->capacity) { \ | |
| return VEC_RESULT_ERR_INTERNALERROR; \ | |
| } | |
| #else | |
| # define VEC_ASSERT(vec, item_size) | |
| #endif | |
| #define VEC_PUSH(type, vec, value) _vec_push_checked((vec), (value), sizeof(type)) | |
| #define VEC_POP(type, vec, out_value) _vec_pop_checked((vec), (out_value), sizeof(type)) | |
| #define VEC_SET_ITEM(type, vec, at, value) _vec_set_item_checked((vec), (at), (value), sizeof(type)) | |
| #define VEC_GET_ITEM(type, vec, at, out_value) _vec_get_item_checked((vec), (at), (out_value), sizeof(type)) | |
| #define VEC_INSERT(type, vec, at, value) _vec_insert_checked((vec), (at), (value), sizeof(type)) | |
| #define VEC_REMOVE(type, vec, at, out_value) _vec_remove_checked((vec), (at), (out_value), sizeof(type)) | |
| #define VEC_SWAP_REMOVE(type, vec, at, out_value) _vec_swap_remove_checked((vec), (at), (out_value), sizeof(type)) | |
| typedef struct { | |
| size_t size; | |
| size_t capacity; | |
| void *data; | |
| size_t item_size; | |
| #ifndef VEC_UNCHECKED | |
| size_t magic_number; | |
| #endif | |
| } vec_t; | |
| typedef enum { | |
| VEC_RESULT_OK = 0, | |
| VEC_RESULT_ERR_UNINITIALIZED, | |
| VEC_RESULT_ERR_INTERNALERROR, | |
| VEC_RESULT_ERR_INVALIDARGUMENT, | |
| VEC_RESULT_ERR_OUTOFBOUNDS, | |
| VEC_RESULT_ERR_EMPTY, | |
| VEC_RESULT_ERR_ALLOC, | |
| VEC_RESULT_ERR_SIZEMISMATCH, | |
| VEC_RESULT_ERR_TOOBIG, | |
| VEC_RESULT_ERR_TRUNCATION, | |
| } vec_result_t; | |
| VEC_DECL | |
| void vec__zero(vec_t *vec) { | |
| vec->data = NULL; | |
| vec->size = vec->capacity = vec->item_size = 0; | |
| #ifndef VEC_UNCHECKED | |
| vec->magic_number = 0; | |
| #endif | |
| } | |
| VEC_DECL | |
| vec_result_t vec_init_with_capacity(vec_t *vec, size_t capacity, size_t item_size) { | |
| if (item_size < 1) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } | |
| vec__zero(vec); | |
| if ((vec->capacity = capacity)) { | |
| if (!(vec->data = calloc(capacity, item_size))) { | |
| return VEC_RESULT_ERR_ALLOC; | |
| } | |
| } | |
| vec->item_size = item_size; | |
| #ifndef VEC_UNCHECKED | |
| vec->magic_number = VEC_MAGIC_NUMBER; | |
| #endif | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_init(vec_t *vec, size_t item_size) { | |
| return vec_init_with_capacity(vec, VEC_INIT_CAPACITY, item_size); | |
| } | |
| VEC_DECL | |
| void vec_free(vec_t *vec) { | |
| if (vec->data != NULL) { | |
| free(vec->data); | |
| } | |
| vec__zero(vec); | |
| } | |
| VEC_DECL | |
| size_t vec_capacity(vec_t *vec) { | |
| return vec->capacity; | |
| } | |
| VEC_DECL | |
| size_t vec_len(vec_t *vec) { | |
| return vec->size; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_resize(vec_t *vec, size_t capacity) { | |
| if (vec->capacity == capacity) { | |
| return VEC_RESULT_OK; | |
| } else if (capacity > SIZE_MAX / vec->item_size) { | |
| return VEC_RESULT_ERR_TOOBIG; | |
| } else if (capacity == 0 && vec->size == 0) { | |
| if (vec->data) { | |
| free(vec->data); | |
| } | |
| vec->data = NULL; | |
| vec->capacity = 0; | |
| return VEC_RESULT_OK; | |
| } | |
| size_t curr_len = vec->item_size * vec->size; | |
| size_t new_cap = capacity * vec->item_size; | |
| if (curr_len > new_cap) { | |
| return VEC_RESULT_ERR_TRUNCATION; | |
| } | |
| void *new_data = realloc(vec->data, new_cap); | |
| if (!new_data) { | |
| return VEC_RESULT_ERR_ALLOC; | |
| } | |
| vec->data = new_data; | |
| vec->capacity = capacity; | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_push(vec_t *vec, void *value) { | |
| if (value == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } | |
| vec_result_t result = VEC_RESULT_OK; | |
| if (!vec->capacity) { | |
| result = vec_resize(vec, VEC_INIT_CAPACITY); | |
| } else if (vec->size == vec->capacity) { | |
| result = vec_resize(vec, vec->size * 2); | |
| } | |
| if (result) { | |
| return result; | |
| } | |
| size_t offset = vec->size++ * vec->item_size; | |
| memmove(((char *)vec->data) + offset, value, vec->item_size); | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_get_item(vec_t *vec, size_t at, void **out_value) { | |
| if (out_value == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (at >= vec->size) { | |
| return VEC_RESULT_ERR_OUTOFBOUNDS; | |
| } | |
| size_t offset = at * vec->item_size; | |
| *out_value = ((char *)vec->data) + offset; | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_set_item(vec_t *vec, size_t at, void *value) { | |
| if (value == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (at >=vec->size) { | |
| return VEC_RESULT_ERR_OUTOFBOUNDS; | |
| } | |
| void *item = ((char *)vec->data) + at * vec->item_size; | |
| memmove(item, value, vec->item_size); | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_pop(vec_t *vec, void *out_value) { | |
| if (out_value == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (vec->size == 0) { | |
| return VEC_RESULT_ERR_EMPTY; | |
| } | |
| vec->size -= 1; | |
| void *item = ((char *)vec->data) + vec->size * vec->item_size; | |
| memmove(out_value, item, vec->item_size); | |
| memset(item, 0, vec->item_size); | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_remove(vec_t *vec, size_t at, void *out_value) { | |
| if (out_value == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (at >= vec->size) { | |
| return VEC_RESULT_ERR_OUTOFBOUNDS; | |
| } else if (at == vec->size - 1) { | |
| return vec_pop(vec, out_value); | |
| } | |
| void *item = ((char *)vec->data) + at * vec->item_size; | |
| memmove(out_value, item, vec->item_size); | |
| void *next_item = (char *)item + vec->item_size; | |
| memmove(item, next_item, (--vec->size - at) * vec->item_size); | |
| memset((char *)vec->data + vec->size * vec->item_size, 0, vec->item_size); | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_swap_remove(vec_t *vec, size_t at, void *out_value) { | |
| if (out_value == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (at >= vec->size) { | |
| return VEC_RESULT_ERR_OUTOFBOUNDS; | |
| } else if (at == vec->size - 1) { | |
| return vec_pop(vec, out_value); | |
| } | |
| void *item = ((char *)vec->data) + at * vec->item_size; | |
| void *last_item = ((char *)vec->data) + --vec->size * vec->item_size; | |
| memmove(out_value, item, vec->item_size); | |
| memmove(item, last_item, vec->item_size); | |
| memset(last_item, 0, vec->item_size); | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_insert(vec_t *vec, size_t at, void *value) { | |
| if (value == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (at > vec->size) { | |
| return VEC_RESULT_ERR_OUTOFBOUNDS; | |
| } else if (at == vec->size) { | |
| return vec_push(vec, value); | |
| } | |
| size_t new_size = vec->size + 1; | |
| if (new_size > vec->capacity) { | |
| vec_result_t result = vec_resize(vec, vec->size * 2); | |
| if (result) { | |
| return result; | |
| } | |
| } | |
| void *item = ((char *)vec->data) + at * vec->item_size; | |
| void *next_item = (char *)item + vec->item_size; | |
| memmove(next_item, item, (vec->size - at) * vec->item_size); | |
| memmove(item, value, vec->item_size); | |
| vec->size += 1; | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_insert_range(vec_t *vec, size_t at, void *values, size_t values_len) { | |
| if (values == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (at > vec->size) { | |
| return VEC_RESULT_ERR_OUTOFBOUNDS; | |
| } else if (values_len == 1) { | |
| return vec_insert(vec, at, values); | |
| } | |
| size_t new_len = vec->size + values_len; | |
| if (new_len > vec->capacity) { | |
| vec_result_t result = vec_resize(vec, new_len); | |
| if (result) { | |
| return result; | |
| } | |
| } | |
| void *src = ((char *)vec->data) + at * vec->item_size; | |
| size_t insert_raw_len = values_len * vec->item_size; | |
| size_t tail_raw_len = (vec->size - at) * vec->item_size; | |
| void *dst = (char *)src + insert_raw_len; | |
| memmove(dst, src, tail_raw_len); | |
| memmove(src, values, insert_raw_len); | |
| vec->size += values_len; | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_insert_range_sparse(vec_t *vec, size_t at, void **values, size_t values_len) { | |
| if (values == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (at > vec->size) { | |
| return VEC_RESULT_ERR_OUTOFBOUNDS; | |
| } else if (values_len == 1) { | |
| return vec_insert(vec, at, *values); | |
| } | |
| for (size_t i = 0; i < values_len; i++) { | |
| if (values[i] == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } | |
| } | |
| size_t new_len = vec->size + values_len; | |
| if (new_len > vec->capacity) { | |
| vec_result_t result = vec_resize(vec, new_len); | |
| if (result) { | |
| return result; | |
| } | |
| } | |
| void *src = ((char *)vec->data) + at * vec->item_size; | |
| size_t insert_raw_len = values_len * vec->item_size; | |
| size_t tail_raw_len = (vec->size - at) * vec->item_size; | |
| void *dst = (char *)src + insert_raw_len; | |
| memmove(dst, src, tail_raw_len); | |
| for (size_t i = 0; i < values_len; i++) { | |
| memmove((char *)src + i * vec->item_size, values[i], vec->item_size); | |
| } | |
| vec->size += values_len; | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t vec_remove_range(vec_t *vec, size_t at, size_t n, void *out_buf) { | |
| if (n < 1 || out_buf == NULL) { | |
| return VEC_RESULT_ERR_INVALIDARGUMENT; | |
| } else if (at >= vec->size || n > vec->size - at) { | |
| return VEC_RESULT_ERR_OUTOFBOUNDS; | |
| } | |
| void *src = ((char *)vec->data) + at * vec->item_size; | |
| size_t raw_len = n * vec->item_size; | |
| memmove(out_buf, src, raw_len); | |
| void *rem_start = (char *)src + raw_len; | |
| size_t rem_len = (vec->size - at - n) * vec->item_size; | |
| if (rem_len > 0) { | |
| memmove(src, rem_start, rem_len); | |
| } | |
| vec->size -= n; | |
| void *trim_start = ((char *)vec->data) + vec->size * vec->item_size; | |
| memset(trim_start, 0, raw_len); | |
| return VEC_RESULT_OK; | |
| } | |
| VEC_DECL | |
| vec_result_t _vec_push_checked(vec_t *vec, void *value, size_t item_size) { | |
| VEC_ASSERT(vec, item_size); | |
| return vec_push(vec, value); | |
| } | |
| VEC_DECL | |
| vec_result_t _vec_get_item_checked(vec_t *vec, size_t at, void **value, size_t item_size) { | |
| VEC_ASSERT(vec, item_size); | |
| return vec_get_item(vec, at, value); | |
| } | |
| VEC_DECL | |
| vec_result_t _vec_set_item_checked(vec_t *vec, size_t at, void *value, size_t item_size) { | |
| VEC_ASSERT(vec, item_size); | |
| return vec_set_item(vec, at, value); | |
| } | |
| VEC_DECL | |
| vec_result_t _vec_pop_checked(vec_t *vec, void *out_value, size_t item_size) { | |
| VEC_ASSERT(vec, item_size); | |
| return vec_pop(vec, out_value); | |
| } | |
| VEC_DECL | |
| vec_result_t _vec_remove_checked(vec_t *vec, size_t at, void *out_value, size_t item_size) { | |
| VEC_ASSERT(vec, item_size); | |
| return vec_remove(vec, at, out_value); | |
| } | |
| VEC_DECL | |
| vec_result_t _vec_swap_remove_checked(vec_t *vec, size_t at, void *out_value, size_t item_size) { | |
| VEC_ASSERT(vec, item_size); | |
| return vec_swap_remove(vec, at, out_value); | |
| } | |
| VEC_DECL | |
| vec_result_t _vec_insert_checked(vec_t *vec, size_t at, void *value, size_t item_size) { | |
| VEC_ASSERT(vec, item_size); | |
| return vec_insert(vec, at, value); | |
| } | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #endif // #ifndef INLINE_VEC_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment