diff --git a/vector.h b/vector.h index b82a7cc..a08a0fd 100644 --- a/vector.h +++ b/vector.h @@ -9,20 +9,24 @@ synchronization mechanisms) - but your own usage may not be. Designed for C23 onwards, but should be compatible with older standards. Guarantees: - - Shallow copies are safe, as long as you stop using the original vector after - copying it. A shallow copy still points to the same data, so if you free the - original vector, the copy will point to freed memory. If you want to make a - deep copy, use vector_deep_copy(). Moves are preferred here with vector_move() - which will transfer ownership of the data to the new vector. + - A vector_t must never be duplicated by copying the struct. Two vector_t values + sharing one data pointer both believe they own it, and whichever is destroyed + or moved from first leaves the other holding freed memory. Copying the pointer + (vector_t* b = a;) is fine - that is one vector with two names, and it must be + destroyed exactly once. + - 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. This is slower than a shallow copy, but safer. - 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. + 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. If you want - to keep using the old data, you should make a deep copy of the vector before - resizing it. + 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. - 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 @@ -296,7 +300,7 @@ static inline int vector_grow(vector_t* vec) { @brief Prunes the vector to free unused memory. If the vector's size is less than its capacity, this function will reallocate the vector's data array to match its size, freeing any unused memory. @param vec A pointer to the vector to be pruned. @return 0 on success, -1 if the vector is NULL or allocation fails. - @attention After calling this function, the vector's capacity will be equal to its size. Any pointers to the old data will be invalidated. If you want to keep using the old data, you should make a deep copy of the vector before pruning it. + @attention After calling this function, the vector's capacity will be equal to its size. Any pointers to the old data will be invalidated. @attention Capacity may never drop below 1, even if the vector is empty. */ static inline int vector_prune(vector_t* vec) { @@ -747,10 +751,11 @@ 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, or the source vector is 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. @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. + @attention Two vectors can only share a data pointer if a vector_t was duplicated by copying the struct, which is never valid. The move is refused rather than freeing a buffer the source still points at. */ static inline int vector_move(vector_t* dest, vector_t** src) { if (!dest || !src || !*src) { @@ -761,6 +766,12 @@ static inline int vector_move(vector_t* dest, vector_t** src) { return 0; // Moving to itself, no action needed } + if (dest->data == (*src)->data) { + return -1; // Refuse to move a vector onto another vector that shares its data + // Realistically, this is unreachable via legal use, but... safety. + // Or something. You WILL trigger this if you do shallow copies. DO NOT! + } + // Destroy the destination vector's existing elements with its own destructor, // then free its data, as it is about to be replaced vector_clear(dest);