final bug patches for vector.h

This commit is contained in:
2026-08-19 23:32:09 +02:00
parent 2c47529d05
commit 027864af1e
+32 -27
View File
@@ -1,12 +1,13 @@
/*
vector.h - Vectors for C
Version: 1.0.0
Version: 2.0.0
Description:
Header-only library for vectors in C. Generally memory safe (when you
use it properly) but not thread-safe (you must implement your own
synchronization mechanisms) - but your own usage may not be.
Designed for C23 onwards, but should be compatible with older standards.
Header-only library for vectors in C. Memory safe against the mistakes it can
detect, but not thread-safe (you must implement your own synchronization
mechanisms).
Designed for C23 onwards, but should be compatible with older standards (tested
from C99 onwards).
Guarantees:
- 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
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
vector_deep_copy().
- Deep copies may be made with vector_deep_copy(), which will copy the data as
well as the vector itself. Deep copies are only available for vectors without
an element destructor, as a byte-wise copy of owning elements would result in
a double free.
vector_deep_copy(), which copies the data as well as the vector itself. Deep
copies are only available for vectors without an element destructor, as a
byte-wise copy of owning elements would result in a double free.
- No gaps in the raw data array.
- 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.
You should re-fetch them with vector_get() afterwards for the new pointers
to preserve them.
- When a vector is resized, the data is reallocated to a new memory location,
so any pointers into the old data are invalidated. There is no way to
preserve them; re-fetch with vector_get() after the resize.
- Order of elements is preserved.
- 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
this and copies the value before it can be invalidated or overwritten.
On a vector with an element destructor this is rejected with -1 instead,
as a byte-wise copy would leave two slots owning the same memory. To
duplicate an owning element, copy what it owns yourself and push that.
of vector_push_back(), vector_insert() and vector_set(). Each of them handles
the aliasing in whichever way suits it, so the value survives the reallocation
or the shifting of elements. On a vector with an element destructor this is
rejected with -1 instead, as a byte-wise copy would leave two slots owning the
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
caller, and do not call the destructor on it. It is the caller's responsibility
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_set_destructor(vec, free_str);
// ... use the vector ...
vector_destroy(&vec); // Takes the address of your pointer, and NULLs it
A warning regarding misuse:
The vector manages its own buffer correctly: no leaks, no double frees of the data
@@ -113,6 +114,7 @@ typedef struct {
} vector_t;
/* 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 int vector_set_destructor(vector_t* vec, vector_destructor_t destructor);
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
memmove((char*)vec->data + (index * vec->element_size),
(char*)vec->data + ((index + 1) * vec->element_size),
(vec->size - index - 1) * vec->element_size);
if (index + 1 < vec->size) {
memmove((char*)vec->data + (index * vec->element_size),
(char*)vec->data + ((index + 1) * vec->element_size),
(vec->size - index - 1) * vec->element_size);
}
vec->size--;
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 (vector_grow(vec) != 0) {
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.
@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.
@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 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.
@@ -834,14 +838,15 @@ static inline vector_t* vector_deep_copy(const vector_t* vec) {
/*
@brief Destroys the vector and frees its memory.
@param vec A pointer to the vector to be destroyed.
@return 0 on success or if the pointer is already NULL (no-op), -1 if the underlying vector is NULL.
@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 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 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.
*/
static inline int vector_destroy(vector_t** vec) {
if (!vec) {
return -1; // Invalid vector
return -1; // Invalid pointer
}
if (!(*vec)) {