fixing blatantly wrong guarantees and behaviour
This commit is contained in:
@@ -9,20 +9,24 @@ synchronization mechanisms) - but your own usage may not be.
|
|||||||
Designed for C23 onwards, but should be compatible with older standards.
|
Designed for C23 onwards, but should be compatible with older standards.
|
||||||
|
|
||||||
Guarantees:
|
Guarantees:
|
||||||
- Shallow copies are safe, as long as you stop using the original vector after
|
- A vector_t must never be duplicated by copying the struct. Two vector_t values
|
||||||
copying it. A shallow copy still points to the same data, so if you free the
|
sharing one data pointer both believe they own it, and whichever is destroyed
|
||||||
original vector, the copy will point to freed memory. If you want to make a
|
or moved from first leaves the other holding freed memory. Copying the pointer
|
||||||
deep copy, use vector_deep_copy(). Moves are preferred here with vector_move()
|
(vector_t* b = a;) is fine - that is one vector with two names, and it must be
|
||||||
which will transfer ownership of the data to the new vector.
|
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
|
- 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.
|
well as the vector itself. Deep copies are only available for vectors without
|
||||||
Deep copies are only available for vectors without an element destructor, as
|
an element destructor, as a byte-wise copy of owning elements would result in
|
||||||
a byte-wise copy of owning elements would result in a double free.
|
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. If you want
|
This means that any pointers to the old data will be invalidated. Full Stop.
|
||||||
to keep using the old data, you should make a deep copy of the vector before
|
You should re-fetch them with vector_get() afterwards for the new pointers
|
||||||
resizing it.
|
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(). 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.
|
@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.
|
@param vec A pointer to the vector to be pruned.
|
||||||
@return 0 on success, -1 if the vector is NULL or allocation fails.
|
@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.
|
@attention Capacity may never drop below 1, even if the vector is empty.
|
||||||
*/
|
*/
|
||||||
static inline int vector_prune(vector_t* vec) {
|
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.
|
@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, 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 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.
|
||||||
|
@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) {
|
static inline int vector_move(vector_t* dest, vector_t** src) {
|
||||||
if (!dest || !src || !*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
|
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,
|
// Destroy the destination vector's existing elements with its own destructor,
|
||||||
// then free its data, as it is about to be replaced
|
// then free its data, as it is about to be replaced
|
||||||
vector_clear(dest);
|
vector_clear(dest);
|
||||||
|
|||||||
Reference in New Issue
Block a user