final bug patches for vector.h

This commit is contained in:
2026-08-19 23:32:09 +02:00
parent 2c47529d05
commit 027864af1e
+29 -24
View File
@@ -1,12 +1,13 @@
/* /*
vector.h - Vectors for C vector.h - Vectors for C
Version: 1.0.0 Version: 2.0.0
Description: Description:
Header-only library for vectors in C. Generally memory safe (when you Header-only library for vectors in C. Memory safe against the mistakes it can
use it properly) but not thread-safe (you must implement your own detect, but not thread-safe (you must implement your own synchronization
synchronization mechanisms) - but your own usage may not be. mechanisms).
Designed for C23 onwards, but should be compatible with older standards. Designed for C23 onwards, but should be compatible with older standards (tested
from C99 onwards).
Guarantees: Guarantees:
- A vector_t must never be duplicated by copying the struct. Two vector_t values - A vector_t must never be duplicated by copying the struct. Two vector_t values
@@ -17,23 +18,21 @@ Guarantees:
- To hand a vector's data to another vector, use vector_move(). The source is - To hand a vector's data to another vector, use vector_move(). The source is
emptied, freed and its pointer set to NULL, so there is only ever one owner. emptied, freed and its pointer set to NULL, so there is only ever one owner.
- To get a second, independent vector with the same contents, use - To get a second, independent vector with the same contents, use
vector_deep_copy(). vector_deep_copy(), which copies the data as well as the vector itself. Deep
- Deep copies may be made with vector_deep_copy(), which will copy the data as copies are only available for vectors without an element destructor, as a
well as the vector itself. Deep copies are only available for vectors without byte-wise copy of owning elements would result in a double free.
an element destructor, as a byte-wise copy of owning elements would result in
a double free.
- No gaps in the raw data array. - No gaps in the raw data array.
- When a vector is resized, the data is reallocated to a new memory location. - When a vector is resized, the data is reallocated to a new memory location,
This means that any pointers to the old data will be invalidated. Full Stop. so any pointers into the old data are invalidated. There is no way to
You should re-fetch them with vector_get() afterwards for the new pointers preserve them; re-fetch with vector_get() after the resize.
to preserve them.
- Order of elements is preserved. - Order of elements is preserved.
- It is safe to pass a pointer into a vector's own data as the source element - It is safe to pass a pointer into a vector's own data as the source element
of vector_push_back(), vector_insert() and vector_set(). The library detects of vector_push_back(), vector_insert() and vector_set(). Each of them handles
this and copies the value before it can be invalidated or overwritten. the aliasing in whichever way suits it, so the value survives the reallocation
On a vector with an element destructor this is rejected with -1 instead, or the shifting of elements. On a vector with an element destructor this is
as a byte-wise copy would leave two slots owning the same memory. To rejected with -1 instead, as a byte-wise copy would leave two slots owning the
duplicate an owning element, copy what it owns yourself and push that. same memory. To duplicate an owning element, copy what it owns yourself and
push that.
- vector_take_at() and vector_take_back() transfer ownership of the element to the - vector_take_at() and vector_take_back() transfer ownership of the element to the
caller, and do not call the destructor on it. It is the caller's responsibility caller, and do not call the destructor on it. It is the caller's responsibility
to free whatever the element owns. to free whatever the element owns.
@@ -57,6 +56,8 @@ belongs to the vector. For a vector of char* this means:
vector_t* vec = vector_create(sizeof(char*)); vector_t* vec = vector_create(sizeof(char*));
vector_set_destructor(vec, free_str); vector_set_destructor(vec, free_str);
// ... use the vector ...
vector_destroy(&vec); // Takes the address of your pointer, and NULLs it
A warning regarding misuse: A warning regarding misuse:
The vector manages its own buffer correctly: no leaks, no double frees of the data The vector manages its own buffer correctly: no leaks, no double frees of the data
@@ -113,6 +114,7 @@ typedef struct {
} vector_t; } vector_t;
/* Forward declarations */ /* Forward declarations */
static inline int vector_is_aliased(const vector_t* vec, const void* ptr);
static inline vector_t* vector_create(size_t element_size); static inline vector_t* vector_create(size_t element_size);
static inline int vector_set_destructor(vector_t* vec, vector_destructor_t destructor); static inline int vector_set_destructor(vector_t* vec, vector_destructor_t destructor);
static inline vector_destructor_t vector_get_destructor(const vector_t* vec); static inline vector_destructor_t vector_get_destructor(const vector_t* vec);
@@ -412,9 +414,11 @@ static inline int vector_pop_at(vector_t* vec, size_t index) {
} }
// Move elements after the index one position to the left // Move elements after the index one position to the left
if (index + 1 < vec->size) {
memmove((char*)vec->data + (index * vec->element_size), memmove((char*)vec->data + (index * vec->element_size),
(char*)vec->data + ((index + 1) * vec->element_size), (char*)vec->data + ((index + 1) * vec->element_size),
(vec->size - index - 1) * vec->element_size); (vec->size - index - 1) * vec->element_size);
}
vec->size--; vec->size--;
return 0; // Success return 0; // Success
@@ -502,7 +506,7 @@ static inline int vector_insert(vector_t* vec, size_t index, const void* element
if (vec->size >= vec->capacity) { if (vec->size >= vec->capacity) {
if (vector_grow(vec) != 0) { if (vector_grow(vec) != 0) {
free(temp); free(temp);
return -1; // Reserve failed return -1; // Failed to grow the vector
} }
} }
@@ -751,7 +755,7 @@ static inline void* vector_as_c_array_mutable(vector_t* vec) {
@brief Moves a vector to another vector, transferring ownership of the data. The destination vector will take ownership of the source vector's data, its element size and its destructor. @brief Moves a vector to another vector, transferring ownership of the data. The destination vector will take ownership of the source vector's data, its element size and its destructor.
@param dest A pointer to the destination vector. @param dest A pointer to the destination vector.
@param src A pointer to the pointer holding the source vector. It will be set to NULL. @param src A pointer to the pointer holding the source vector. It will be set to NULL.
@return 0 on success, -1 if the destination vector is NULL, the source pointer is NULL the source vector is NULL, or both vectors share the same data pointer. @return 0 on success, -1 if the destination vector is NULL, the source pointer is NULL, the source vector is NULL, or both vectors share the same data pointer.
@attention After calling this function, the source vector will be freed (excluding data) and the source pointer will be set to NULL, so it cannot be used again. @attention After calling this function, the source vector will be freed (excluding data) and the source pointer will be set to NULL, so it cannot be used again.
@attention The destination vector's existing elements will be destroyed (using the destination's own destructor, if it has one) and its data freed. Ensure that you do not need the existing data before calling this function. @attention The destination vector's existing elements will be destroyed (using the destination's own destructor, if it has one) and its data freed. Ensure that you do not need the existing data before calling this function.
@attention Moving a vector onto itself is a no-op and reports success, leaving the vector untouched. @attention Moving a vector onto itself is a no-op and reports success, leaving the vector untouched.
@@ -834,14 +838,15 @@ static inline vector_t* vector_deep_copy(const vector_t* vec) {
/* /*
@brief Destroys the vector and frees its memory. @brief Destroys the vector and frees its memory.
@param vec A pointer to the vector to be destroyed. @param vec A pointer to the pointer holding the vector to be destroyed. It will be set to NULL.
@return 0 on success or if the pointer is already NULL (no-op), -1 if the underlying vector is NULL. @return 0 on success, or if the vector was already NULL. -1 only if the pointer itself is NULL.
@attention After calling this function, the vector pointer should not be used again (It will be set to NULL). Accessing it after destruction will lead to undefined behavior. @attention After calling this function, the vector pointer should not be used again (It will be set to NULL). Accessing it after destruction will lead to undefined behavior.
@attention Only the pointer you pass in is set to NULL. Copies of that pointer held elsewhere are left dangling and must not be used.
@attention If stored elements own memory of their own, set a destructor with vector_set_destructor() to have it cleaned up here. Otherwise, the vector will only free the memory allocated for the data array and the vector structure itself, but not any dynamically allocated memory within the elements. @attention If stored elements own memory of their own, set a destructor with vector_set_destructor() to have it cleaned up here. Otherwise, the vector will only free the memory allocated for the data array and the vector structure itself, but not any dynamically allocated memory within the elements.
*/ */
static inline int vector_destroy(vector_t** vec) { static inline int vector_destroy(vector_t** vec) {
if (!vec) { if (!vec) {
return -1; // Invalid vector return -1; // Invalid pointer
} }
if (!(*vec)) { if (!(*vec)) {