From 027864af1efbc9b496a5437ab87572f93f9c5a21 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Wed, 19 Aug 2026 23:32:09 +0200 Subject: [PATCH] final bug patches for vector.h --- vector.h | 59 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/vector.h b/vector.h index e96f138..7a8a534 100644 --- a/vector.h +++ b/vector.h @@ -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)) {