diff --git a/LICENSE b/LICENSE index 03fd922..349dbf6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,21 @@ -Copyright (c) 2026 Jonas Korene Novak +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Copyright (c) 2026 DcruBro -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 8c815fb..ada503e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # DLibC Some custom C types and thing I often use. +These are generally header-only, but if they have source files, they'll be in their own directories + +They're generally designed for C23 onwards, but "should" work with older versions. + +Licensed under the MIT License (because GPL doesn't make sense for this) + +Copyright (c) 2026 DcruBro diff --git a/vector.h b/vector.h new file mode 100644 index 0000000..8a0d10e --- /dev/null +++ b/vector.h @@ -0,0 +1,571 @@ +/* +vector.h - Vectors for C +Version: 1.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. + +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. + - 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. + - 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. + - Order of elements is preserved. + +License: +MIT License + +Copyright (c) 2026 DcruBro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef VECTOR_H +#define VECTOR_H + +#include +#include +#include + +typedef void (*vector_destructor_t)(void* element); // Function pointer type for element destructor + +typedef struct { + size_t size; // Number of elements in the vector + size_t capacity; // Allocated capacity of the vector + size_t element_size; // Size of each element in the vector + void *data; // Pointer to the raw data array + vector_destructor_t destructor; // Function pointer to the element destructor +} vector_t; + +/* + @brief Creates a new vector with the specified element size. + @param element_size The size of each element in the vector. Call with sizeof(type) + @return A pointer to the newly created vector, or NULL if allocation fails. The vector lives on the heap. + @attention The vector must be destroyed with vector_destroy() to free its memory. Failing to do so will result in a memory leak. + @attention The vector's initial capacity is set to 10. If you want to change the initial capacity globally, set the DLIBC_VECTOR_INITIAL_CAPACITY macro before including this header file. The initial capacity must be greater than 0. + @attention The vector's element size must be greater than 0. If you pass 0, the function will return NULL. +*/ +static inline vector_t* vector_create(size_t element_size) { + vector_t* vec = (vector_t*)malloc(sizeof(vector_t)); + if (!vec) { + return NULL; // Allocation failed + } + + if (element_size == 0) { + free(vec); + return NULL; // Invalid element size + } + + #ifndef DLIBC_VECTOR_INITIAL_CAPACITY + size_t initial_capacity = 10; // Default initial capacity + #else + #if DLIBC_VECTOR_INITIAL_CAPACITY <= 0 + #error "DLIBC_VECTOR_INITIAL_CAPACITY must be greater than 0" + #endif + size_t initial_capacity = DLIBC_VECTOR_INITIAL_CAPACITY > 0 ? DLIBC_VECTOR_INITIAL_CAPACITY : 10; // Use the macro if defined and greater than 0, otherwise default to 10 + #endif + + if (SIZE_MAX / element_size < initial_capacity) { + free(vec); + return NULL; // Prevent overflow + } + + vec->size = 0; + vec->capacity = initial_capacity; + vec->element_size = element_size; + vec->data = malloc(vec->capacity * vec->element_size); + vec->destructor = NULL; // Initialize destructor to NULL + if (!vec->data) { + free(vec); + return NULL; // Allocation failed + } + + return vec; +} + +/* + @brief Sets the destructor function for the vector's elements. This function will be called on each element when the vector is destroyed, allowing for custom cleanup of dynamically allocated memory within the elements. + @param vec A pointer to the vector for which to set the destructor. + @param destructor A function pointer to the destructor function. The function should take a single void* parameter, which will be a pointer to the element to be destroyed. + @return 0 on success, -1 if the vector is NULL. + @attention If you do not set a destructor function, the vector will not automatically free any dynamically allocated memory within its elements when it is destroyed. You must ensure that you free any such memory manually before destroying the vector to avoid memory leaks. + @attention Honestly, you can just directly assign this with vec->destructor = destructor; but this is a more "user-friendly" way to do it. + @attention This assumes that your destructor function is valid and safe to call. If it isn't, bad things will happen and it will not be nice to watch. +*/ +static inline int vector_set_destructor(vector_t* vec, vector_destructor_t destructor) { + if (vec) { + vec->destructor = destructor; + } else { + return -1; // Invalid vector + } + + return 0; +} + +/* + @brief Reserves space for the specified capacity in the vector. + @param vec A pointer to the vector for which to reserve space. + @param new_capacity The new capacity of the vector. + @return 0 on success, -1 if the vector is NULL or allocation fails. + @attention You cannot reserve space for zero or below the current size (doing so will return -1). +*/ +static inline int vector_reserve(vector_t* vec, size_t new_capacity) { + if (!vec) { + return -1; // Invalid vector + } + + if (new_capacity == 0 || new_capacity < vec->size) { + return -1; // Cannot reserve space for zero or below current size + } + + if (new_capacity > SIZE_MAX / vec->element_size) { + return -1; // Prevent overflow + } + + void* new_data = realloc(vec->data, new_capacity * vec->element_size); + if (!new_data) { + return -1; // Allocation failed + } + + vec->data = new_data; + vec->capacity = new_capacity; + + return 0; // Success +} + +/* + @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 Capacity may never drop below 1, even if the vector is empty. +*/ +static inline int vector_prune(vector_t* vec) { + if (!vec) { + return -1; // Invalid vector + } + + if (vec->size < vec->capacity) { + size_t new_capacity = vec->size > 0 ? vec->size : 1; // Ensure capacity is at least 1 + + if (new_capacity > SIZE_MAX / vec->element_size) { + return -1; // Prevent overflow + } + + void* new_data = realloc(vec->data, new_capacity * vec->element_size); + if (!new_data) { + return -1; // Allocation failed + } + + vec->data = new_data; + vec->capacity = new_capacity; + } + + return 0; // Success +} + +/* + @brief Adds an element to the end of the vector. + @param vec A pointer to the vector to which the element will be added. + @param element A pointer to the element to be added. The element will be copied into the vector's data array. + @return 0 on success, -1 if the vector is NULL, the element is NULL, or if reservation fails. + @attention The element must be a pointer to a valid memory location containing data of the same type as the vector's element type. The vector will make a copy of the data, so the original element can be modified or freed after this function returns. +*/ +static inline int vector_push_back(vector_t* vec, const void* element) { + if (!vec || !element) { + return -1; // Invalid vector or element + } + + if (vec->size >= vec->capacity) { + // Reserve space for the vector to double its current capacity + if (vector_reserve(vec, vec->capacity * 2) != 0) { + return -1; // Reserve failed + } + } + + // Copy the new element into the vector's data array + memcpy((char*)vec->data + (vec->size * vec->element_size), element, vec->element_size); + vec->size++; + + return 0; // Success +} + +/* + @brief Removes the last element from the vector. + @param vec A pointer to the vector from which the element will be removed. + @return 0 on success, -1 if the vector is NULL or empty. + @attention After calling this function, the vector's size will be reduced by one. The memory occupied by the removed element will not be freed automatically. +*/ +static inline int vector_pop_back(vector_t* vec) { + if (!vec || vec->size == 0) { + return -1; // Invalid vector or empty vector + } + + if (vec->destructor) { + void* element = (char*)vec->data + ((vec->size - 1) * vec->element_size); + vec->destructor(element); // Call the destructor for the last element + } + + vec->size--; + return 0; // Success +} + +/* + @brief Removes the element at the specified index from the vector. + @param vec A pointer to the vector from which the element will be removed. + @param index The index of the element to be removed. + @return 0 on success, -1 if the vector is NULL or the index is out of bounds. + @attention After calling this function, the vector's size will be reduced by one. The memory occupied by the removed element will not be freed automatically. +*/ +static inline int vector_pop_at(vector_t* vec, size_t index) { + if (!vec || index >= vec->size) { + return -1; // Invalid vector or index out of bounds + } + + if (vec->destructor) { + void* element = (char*)vec->data + (index * vec->element_size); + vec->destructor(element); // Call the destructor for the element to be removed + } + + // 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); + + vec->size--; + return 0; // Success +} + +/* + @brief Inserts an element at the specified index in the vector. + @param vec A pointer to the vector into which the element will be inserted. + @param index The index at which to insert the element. + @param element A pointer to the element to be inserted. + @return 0 on success, -1 if the vector is NULL, the element is NULL, or if reservation fails. + @attention After calling this function, the vector's size will be increased by one. The memory occupied by the inserted element will not be freed automatically. +*/ +static inline int vector_insert(vector_t* vec, size_t index, const void* element) { + if (!vec || !element || index > vec->size) { + return -1; // Invalid vector, element, or index out of bounds + } + + if (vec->size >= vec->capacity) { + // Reserve space for the vector to double its current capacity + if (vector_reserve(vec, vec->capacity * 2) != 0) { + return -1; // Reserve failed + } + } + + // Move elements after the index one position to the right + memmove((char*)vec->data + ((index + 1) * vec->element_size), + (char*)vec->data + (index * vec->element_size), + (vec->size - index) * vec->element_size); + + // Copy the new element into the vector's data array at the specified index + memcpy((char*)vec->data + (index * vec->element_size), element, vec->element_size); + vec->size++; + + return 0; // Success +} + +/* + @brief Clears all elements from the vector. + @param vec A pointer to the vector to be cleared. + @return 0 on success, -1 if the vector is NULL. + @attention After calling this function, the vector's size will be zero. The memory occupied by the elements will not be freed automatically. +*/ +static inline int vector_clear(vector_t* vec) { + if (!vec) { + return -1; // Invalid vector + } + + if (vec->destructor) { + for (size_t i = 0; i < vec->size; ++i) { + void* element = (char*)vec->data + (i * vec->element_size); + vec->destructor(element); // Call the destructor for each element + } + } + + vec->size = 0; + return 0; // Success +} + +/* + @brief Sets the element at the specified index in the vector. + @param vec A pointer to the vector in which to set the element. + @param index The index of the element to set. + @param element A pointer to the element to set. + @return 0 on success, -1 if the vector is NULL, the element is NULL, or if the index is out of bounds. + @attention The memory occupied by the element will not be freed automatically. +*/ +static inline int vector_set(vector_t* vec, size_t index, const void* element) { + if (!vec || !element || index >= vec->size) { + return -1; // Invalid vector, element, or index out of bounds + } + + memcpy((char*)vec->data + (index * vec->element_size), element, vec->element_size); + return 0; // Success +} + +/* + @brief Checks if the vector is empty. + @param vec A pointer to the vector to check. + @return 1 if the vector is empty, 0 if it is not empty. + @attention If the vector is NULL, this function will return 1 (is empty) to indicate that the vector is invalid. +*/ +static inline int vector_is_empty(const vector_t* vec) { + if (!vec) { + return 1; // Invalid vector + } + + return vec->size == 0; +} + +/* + @brief Gets a pointer to the last element in the vector. + @param vec A pointer to the vector from which to get the element. + @return A pointer to the last element in the vector, or NULL if the vector is NULL or empty. + @attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed. +*/ +static inline void* vector_back(vector_t* vec) { + if (!vec || vec->size == 0) { + return NULL; // Invalid vector or empty vector + } + + return (char*)vec->data + ((vec->size - 1) * vec->element_size); +} + +/* + @brief Gets a const pointer to the last element in the vector. Cannot be used to modify the element. + @param vec A pointer to the vector from which to get the element. + @return A constant pointer to the last element in the vector, or NULL if the vector is NULL or empty. + @attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed. +*/ +static inline const void* vector_back_const(const vector_t* vec) { + if (!vec || vec->size == 0) { + return NULL; // Invalid vector or empty vector + } + + return (const char*)vec->data + ((vec->size - 1) * vec->element_size); +} + +/* + @brief Gets a pointer to the element at the specified index in the vector. + @param vec A pointer to the vector from which to get the element. + @param index The index of the element to get. + @return A pointer to the element at the specified index, or NULL if the vector is NULL or the index is out of bounds. + @attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed. + @attention For most read-only operations, consider using vector_get_const() instead, which returns a const pointer to the element. +*/ +static inline void* vector_get(vector_t* vec, size_t index) { + if (!vec || index >= vec->size) { + return NULL; // Invalid vector or index out of bounds + } + + return (char*)vec->data + (index * vec->element_size); +} + +/* + @brief Gets a constant pointer to the element at the specified index in the vector. Cannot be used to modify the element. + @param vec A pointer to the vector from which to get the element. + @param index The index of the element to get. + @return A constant pointer to the element at the specified index, or NULL if the vector is NULL or the index is out of bounds. + @attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed. +*/ +static inline const void* vector_get_const(const vector_t* vec, size_t index) { + if (!vec || index >= vec->size) { + return NULL; // Invalid vector or index out of bounds + } + + return (const char*)vec->data + (index * vec->element_size); +} + +/* + @brief Gets the size of the vector. + @param vec A pointer to the vector whose size is to be retrieved. + @return The size of the vector, or 0 if the vector is NULL. +*/ +static inline size_t vector_size(const vector_t* vec) { + if (!vec) { + return 0; // Invalid vector + } + + return vec->size; +} + +/* + @brief Gets the capacity of the vector. + @param vec A pointer to the vector whose capacity is to be retrieved. + @return The capacity of the vector, or 0 if the vector is NULL. +*/ +static inline size_t vector_capacity(const vector_t* vec) { + if (!vec) { + return 0; // Invalid vector + } + + return vec->capacity; +} + +/* + @brief Gets the size of each element in the vector. + @param vec A pointer to the vector whose element size is to be retrieved. + @return The size of each element in the vector, or 0 if the vector is NULL. +*/ +static inline size_t vector_element_size(const vector_t* vec) { + if (!vec) { + return 0; // Invalid vector + } + + return vec->element_size; +} + +/* + @brief Gets a pointer to the underlying C array of the vector. Cannot modify the vector through this pointer. + @param vec A pointer to the vector whose underlying array is to be retrieved. + @return A pointer to the underlying C array, or NULL if the vector is NULL. + @attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed. +*/ +static inline const void* vector_as_c_array(const vector_t* vec) { + if (!vec) { + return NULL; // Invalid vector + } + + return vec->data; +} + +/* + @brief Gets a pointer to the underlying C array of the vector. Can be used to modify the vector's elements. + @param vec A pointer to the vector whose underlying array is to be retrieved. + @return A pointer to the underlying C array, or NULL if the vector is NULL. + @attention The returned pointer is a generic. You should cast it to the appropriate type before using it. The pointer will become invalid if the vector is resized or destroyed. +*/ +static inline void* vector_as_c_array_mutable(vector_t* vec) { + if (!vec) { + return NULL; // Invalid vector + } + + return vec->data; +} + +/* + @brief Moves a vector to another vector, transferring ownership of the data. The source vector pointer can be safely discarded after this operation, as it will be set to NULL. The destination vector will take ownership of the source vector's data. + @param dest A pointer to the destination vector. + @param src A pointer to the source vector. + @return 0 on success, -1 if either vector is NULL. + @attention After calling this function, the source vector will be freed (excluding data). The source pointer will be set to NULL. The destination vector will take ownership of the source vector's data. + @attention The destination vector's existing data will be freed if it already has allocated memory. Ensure that you do not need the existing data before calling this function. +*/ +static inline int vector_move(vector_t* dest, vector_t** src) { + if (!dest || !src || !*src) { + return -1; // Invalid vectors + } + + if (dest == *src) { + return 0; // Moving to itself, no action needed + } + + // Free the destination vector's data if it already has allocated memory + if (dest->data) { + free(dest->data); + } + + // Transfer ownership of the source vector's data to the destination vector + dest->size = (*src)->size; + dest->capacity = (*src)->capacity; + dest->element_size = (*src)->element_size; + dest->data = (*src)->data; + + // Reset the source vector to an empty state + (*src)->size = 0; + (*src)->capacity = 0; + (*src)->element_size = 0; + (*src)->data = NULL; + + free(*src); // Free the source vector structure, but not its data (ownership transferred) + *src = NULL; + + return 0; // Success +} + +/* + @brief Creates a deep copy of the vector, including its data. + @param vec A pointer to the vector to be copied. + @return A pointer to the newly created deep copy of the vector, or NULL if allocation fails or if the input vector is NULL. + @attention The returned vector must be destroyed with vector_destroy() to free its memory. Failing to do so will result in a memory leak. +*/ +static inline vector_t* vector_deep_copy(const vector_t* vec) { + if (!vec) { + return NULL; // Invalid vector + } + + vector_t* new_vec = vector_create(vec->element_size); + if (!new_vec) { + return NULL; // Allocation failed + } + + int r = vector_reserve(new_vec, vec->capacity); + if (r != 0) { + vector_destroy(new_vec); + return NULL; // Allocation failed + } + + if (vec->size > 0) { + memcpy(new_vec->data, vec->data, vec->size * vec->element_size); + new_vec->size = vec->size; + } // vector_create() already sets size to 0 by default + + return new_vec; +} + +/* + @brief Destroys the vector and frees its memory. + @param vec A pointer to the vector to be destroyed. + @return 0 on success, -1 if the vector is NULL. + @attention After calling this function, the vector pointer should not be used again. Accessing it after destruction will lead to undefined behavior. + @attention If stored elements are structs, consider setting the destructor function pointer to the vector_t struct to allow for custom cleanup of elements. 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 + } + + if (vec->destructor) { + for (size_t i = 0; i < vec->size; ++i) { + void* element = (char*)vec->data + (i * vec->element_size); + vec->destructor(element); // Call the destructor for each element + } + } + + free(vec->data); + free(vec); + return 0; // Success +} + +#endif // VECTOR_H