diff options
-rw-r--r-- | Makefile | 20 | ||||
-rw-r--r-- | README.md | 29 | ||||
-rw-r--r-- | compile_flags.txt | 1 | ||||
-rw-r--r-- | include/ccmake.h | 0 | ||||
-rw-r--r-- | include/dynarray.h | 192 | ||||
-rw-r--r-- | include/embed.h | 0 | ||||
-rw-r--r-- | include/hashset.h | 0 | ||||
-rw-r--r-- | include/string.h | 0 | ||||
-rw-r--r-- | include/utility.h | 76 | ||||
-rw-r--r-- | tests/dynarray.c | 82 |
10 files changed, 400 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6dbbfe7 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ + +CC := gcc +CFLAGS := -g -Wall -Wextra -Wpedantic -Werror + + +tests: dynarray + + +dynarray: tests/dynarray.c + $(CC) $(CFLAGS) -o $@ $^ -DTT_CHAR + ./dynarray + $(CC) $(CFLAGS) -o $@ $^ -DTT_SHORT + ./dynarray + $(CC) $(CFLAGS) -o $@ $^ -DTT_INT + ./dynarray + $(CC) $(CFLAGS) -o $@ $^ -DTT_LONG + ./dynarray + + rm dynarray + diff --git a/README.md b/README.md new file mode 100644 index 0000000..137e97b --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# What is this? +This is just a collection of (usually single-purpos) header files that I use refularly in my code +# Common conventions +* As these are header-only libraries, there is a macro in form `HEADERNAME_IMPLEMENTATION` that has to be defined before including the header. A header can only be included once while this macro is defined, otherwise you get multiple definition errors. +* Names starting with underscores are kind of private-use for this libraries, but they may be declared not `static` because they are used in macros that expose API. +* `snake_case` +* Function-like macros are not all-caps. +* For now i only support GCC-based compilers. I will consider removing this dependency in future (e.g. getting rid of that [`__qsort_cmps` declaration](include/utility.h)). +# Descriptions for heders: +## [`dynarray.h`](include/dynarray.h) +* Summary: A flexible and easy-to-use dynamically-sized array implementation based on the idea of storing metadata about array to the left of actual data. this allows it to be used interchangabely as a regular C array and as dynamic array. +* How to use: Define `DYNARRAY_IMPLEMENTATION` macro before including `dynarray.h`. If you are using functions to sort the array (`array_qsort_integral`), you also have to define `UTILITY_IMPLEMENTATION` (as it uses integer comparison functions from there) +* Examples: See [tests](tests/dynarray.c) +## [`utility.h`](include/utility.h) +* Summary: Some commonly-used macros and functions in this library, could as well be called `miscellaneous`. +* How to use: Define `UTILITY_IMPLEMENTATION` macro before including `utility.h`. If you want it to expose helper functions/variables (which you may want, check the source), define macro `UTILITY_EXPOSE_HELPERS`. +* Examples: there are kind of no examples for this, i guess you could search for the usages in [`dynarray.h`](include/dynarray.h) +## [`embed.h`](include/embed.h) +* Summary: Code generator for embedding resources directly into an executable. +* How to use: It exposes C interface, so in order to use it, you will need to have a C program that builds resources for you. It has it's own repo, but i will be adding CLI to there soon. +* Examples: see [this repo](https://github.com/justanothercatgirl/embed_test.c/) +## `build.h` +nearest TODO for now. +* Summary: a build system based on C. To compile something under it, you do something like `cc -o builder builder.c && ./builder`. The idea is stolen from [Tsoding](https://example.com) +# Warning! +Everything here is written by a relatively inexperienced student (me), so I guarantee basically nothing. Memory leaks? I am sorry for them, but also not responsible. Security? Haven't heard of that either. That's just how it is. But I try to make everything as good as possible, so you can use the code after a careful review. +# License +I did not decide on it yet, but I am inclined towards MIT or even public domain. Don't want to restrict the usage with LGPL, because it's not like this code is a big deal, anyone could write it. Also, I don't want to deal with license issues that I created for myself in future, so yeah, that's how it is. For now, public domain. + diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..1a9ce62 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1 @@ +-DTT_INT diff --git a/include/ccmake.h b/include/ccmake.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/include/ccmake.h diff --git a/include/dynarray.h b/include/dynarray.h new file mode 100644 index 0000000..bdb9db9 --- /dev/null +++ b/include/dynarray.h @@ -0,0 +1,192 @@ +#ifndef JUSTANOTHERCATGIRL_HEADERS_DYNARRAY +#define JUSTANOTHERCATGIRL_HEADERS_DYNARRAY + +//! A dynamic array implementation +//! As this is a header-only library, you have to define `DYNARRAY_IMPLEMENTATION` macro the first time you include this +//! header. This implementation uses the idea of storing metadata to the left of the array data, so that the user can +//! think of it (and use it) as normal array. Most of the API functions are defined as macros that either do some work +//! or call the implementation. They are not all-caps because they are more like functions. +//! As source is the best documentation, you should check yourself what is macro and what is not. generally, everything +//! prefixed with `array_` is a macro. Some of the macros evaluate their arguments more than once (the multiline ones), +//! so as a rule of thumb, you should only ever pass evaluated identifiers or expressions with no side-effects (like `ptr+2`) +//! to all of the macros in this library. + +#include "utility.h" +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/// size of the array header. should not be used directly, unless you know what you are doing +#define DYNARRAY_HEADER_SIZE sizeof(struct _dynarray_header) + +/// allocate new array of <size> elements of type <type>. +#define array_new(type, size) _alloc_dynarray(sizeof(type), size) +/// get amount of elements in the array +#define array_size(array) (((struct _dynarray_header *)((byte *)(array)-DYNARRAY_HEADER_SIZE))->size) +/// get array capacity (it's maximum size unitl reallocation) +#define array_capacity(array) (((struct _dynarray_header *)((byte *)(array)-DYNARRAY_HEADER_SIZE))->capacity) +/// get element size of an array (same as sizeof(array[0]), except not constant-expression) +#define array_element_size(array) (((struct _dynarray_header *)((byte *)(array)-DYNARRAY_HEADER_SIZE))->memb_size) +/// free a dynamic array +#define array_free(array) free(array_header(array)) +/// get dynamic array header. should not be used directly, unless you know what you are doing. +#define array_header(array) ((struct _dynarray_header *)(((byte *)array) - DYNARRAY_HEADER_SIZE)) +/// allocate new array of <size> element of type <type> and initialize it from buffer +#define array_new_buffer_copy(type, buffer, size) _alloc_dynarray_buffer(sizeof(type), size, buffer, sizeof(type) * size) +/// add one new element to the end of the array. May invalidate previous pointer if size+1 > capacity. +#define array_push(array, element) \ + do { \ + struct _dynarray_header *_ahdr = array_header(array); \ + if (_ahdr->size + 1 > _ahdr->capacity) { \ + array = _memreserve_dynarray(array, 1L << upper_2_power_64(_ahdr->size + 1)); \ + _ahdr = array_header(array); \ + } \ + array[_ahdr->size] = (element); \ + ++_ahdr->size; \ + } while (0) +/// inserts element at given index element is evaluated once. +/// Note: `element` HAS to be of same type as the array during initialization. The expression has to be of the same type. +/// That means calling `array_insert(arr, 0, 5)`, where array is of type `long`, WILL lead to errors. because typeof(0) is +/// int, and it will put it in an `int` variable and copy 8 bytes from there, which will be some random stack garbage. +#define array_insert(array, element, idx) \ + do { \ + typeof(element) __tmp_el_var = (element); \ + array = _insert_to_index_dynarray(array, &__tmp_el_var, idx); \ + } while(0) +/// removes last element from the array +#define array_pop(array) --array_header(array)->size +/// reserve length elements so that subsequent (length - current_size) operations require no reallocation +#define array_reserve(array, length) if (typeof(array) __tmp; (__tmp = _memreserve_dynarray(array, length)) != NULL) array = __tmp +/// change size of an array. the capacity is set to lowest possible power of 2 that is greater than length +#define array_resize(array, length) array = _force_resize_dynarray(array, length); +/// set capacity to minimum possible value +#define array_shrink(array) array = _memshrink_array(array) +/// bound-checks and returns a pointer to that element. on error returns NULL +#define array_at(array, idx) (idx < array_size(array) ? array[idx] : NULL) +/// sorts the array using compare_func for comparison +#define array_qsort(array, compare_func) qsort(array, array_size(array), array_element_size(array), compare_func) +/// sorts the array using pre-defined compariton functions for signed integers based on size (1, 2, 4, 8) +#define array_qsort_integral(array) array_qsort(array, __qsort_cmps[array_element_size(array)]) +/// Placed here because is not a macro. compares 2 arrays and returns the same way as `strcmp` +int array_compare(const void *const a1, const void *const a2, qsort_cmp_t comp); + + +/// for internal use +enum _dynarray_header_idx { + _dah_idx_member_size = 0, + _dah_idx_capacity = 1, + _dah_idx_size = 2, +}; +/// basically for structurization +struct _dynarray_header { + size_t memb_size; + size_t capacity; + size_t size; +}; + +void *_alloc_dynarray(size_t el_size, size_t len); +void *_alloc_dynarray_buffer(size_t el_size, size_t alen, void *buffer, size_t blen); +/// Forcibly change size AND capacity of the array to new value +/// ignores the power-of-2-capacity rule +void *_force_resize_dynarray(void *dynarray, size_t new_size); +void *_memreserve_dynarray(void *dynarray, size_t reserved); +void *_memshrink_array(void *dynarray); +void *_insert_to_index_dynarray(void *const dynarray, const void *const element, size_t index); + + +#ifdef DYNARRAY_IMPLEMENTATION + +void *_alloc_dynarray(size_t el_size, size_t len) +{ + byte *data = (byte *)malloc(el_size * len + DYNARRAY_HEADER_SIZE); + if (data == NULL) return NULL; + struct _dynarray_header *header = (struct _dynarray_header *)data; + header->size = len; + header->capacity = len; + header->memb_size = el_size; + return data + DYNARRAY_HEADER_SIZE; +} + +/// assumes that element size in the buffer is the same as element size in the array +void *_alloc_dynarray_buffer(size_t el_size, size_t alen, void *buffer, size_t blen) { + void *array = _alloc_dynarray(el_size, alen); + memcpy(array, buffer, el_size * blen); + return array; +} + +void *_memreserve_dynarray(void * const dynarray, size_t reserved) +{ + struct _dynarray_header *hdr = array_header(dynarray); + if (hdr->capacity >= reserved) return dynarray; + byte *data = (byte *)hdr; + data = (byte *)realloc(data, reserved * hdr->memb_size + DYNARRAY_HEADER_SIZE); + if (data == NULL) return NULL; + hdr = (struct _dynarray_header *)data; + hdr->capacity = reserved; + return data + DYNARRAY_HEADER_SIZE; +} + +void *_memshrink_array(void * const dynarray) +{ + struct _dynarray_header *hdr = array_header(dynarray); + byte *data = (byte *)hdr; + data = (byte*) realloc(data, hdr->size * hdr->memb_size + DYNARRAY_HEADER_SIZE); + if (data == NULL) return NULL; + hdr = (struct _dynarray_header *) data; + hdr->capacity = hdr->size; + return data + DYNARRAY_HEADER_SIZE; +} + + +void *_force_resize_dynarray(void * const dynarray, size_t new_size) +{ + struct _dynarray_header *hdr = array_header(dynarray); + if (new_size < hdr->size) { + hdr->size = new_size; + hdr->capacity = 1 << upper_2_power_64(new_size); + byte *arr = (byte*) realloc(hdr, hdr->memb_size * hdr->capacity + DYNARRAY_HEADER_SIZE); + if (arr == NULL) return NULL; + return arr + DYNARRAY_HEADER_SIZE; + + } else { + void* arr; + if ((arr = _memreserve_dynarray(dynarray, 1 << upper_2_power_64(new_size))) == NULL) return NULL; + hdr = array_header(arr); + hdr->size = new_size; + return arr; + } +} + +void *_insert_to_index_dynarray(void *const dynarray, const void *const element, size_t index) { + struct _dynarray_header* hdr = array_header(dynarray); + byte* array = (byte*) _memreserve_dynarray(dynarray, 1 << upper_2_power_64(hdr->size + 1)); + hdr = array_header(array); + ++hdr->size; + memmove(array + hdr->memb_size * (index+1), array + hdr->memb_size * (index), hdr->memb_size * (hdr->size - 1 - index)); + memcpy(array + hdr->memb_size * index, element, hdr->memb_size); + return array; +} + +int array_compare(const void *const a1, const void *const a2, qsort_cmp_t comp) { + struct _dynarray_header *hdr = array_header(a1); + struct _dynarray_header *hdr2 = array_header(a2); + assert(hdr->memb_size == hdr2->memb_size && "The arrays being compared do not have same element size"); + const size_t s1 = hdr->size, s2 = hdr->size; + const size_t elsize = hdr->memb_size; + size_t i; + for (i = 0; i < s1 && i < s2; ++i) { + int result = comp((byte*) a1 + elsize*i, (byte*) a2 + elsize*i); + if (result) return result; + } + if (s1 == s2) return 0; + if (i == s1) return -1; + if (i == s2) return 1; +#define NDEBUG + assert(0); +#undef NDEBUG +} + +#endif // DYNARRAY_IMPLEMENTATION + +#endif // JUSTANOTHERCATGIRL_HEADERS_DYNARRAY diff --git a/include/embed.h b/include/embed.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/include/embed.h diff --git a/include/hashset.h b/include/hashset.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/include/hashset.h diff --git a/include/string.h b/include/string.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/include/string.h diff --git a/include/utility.h b/include/utility.h new file mode 100644 index 0000000..4f264f0 --- /dev/null +++ b/include/utility.h @@ -0,0 +1,76 @@ +#ifndef JUSTANOTHERCATGIRL_HEADERS_UTILITY +#define JUSTANOTHERCATGIRL_HEADERS_UTILITY + +#define stringify(val) _stringify_helper(val) +#define _stringify_helper(val) #val + +#define struct_member_size(type, member) sizeof(( (type*)0 )->member) +typedef unsigned char byte; +#define upper_2_power_32(number) (32 - __builtin_clz(number)) +#define upper_2_power_64(number) (64 - __builtin_clzl(number)) + +typedef int(*qsort_cmp_t)(const void*, const void*); +#define get_qsort_cmp(type) __qsort_cmps[sizeof(type)] +extern const qsort_cmp_t __qsort_cmps[]; + +#ifdef UTILITY_EXPOSE_HELPERS + #define _UTILITY_STATIC + int __default_char_cmp(const void* a, const void* b); + int __default_short_cmp(const void* a, const void* b); + int __default_int_cmp(const void* a, const void* b); + int __default_long_cmp(const void* a, const void* b); + int __default_long_long_cmp(const void* a, const void* b); +#else + #define _UTILITY_STATIC static +#endif // UTILITY_EXPOSE_HELPERS + + +#ifdef UTILITY_IMPLEMENTATION + +_UTILITY_STATIC int __default_char_cmp(const void* a, const void* b) { + char x = *(char*)a, y = *(char*)b; + if (x < y) return -1; + if (x > y) return 1; + return 0; +} +_UTILITY_STATIC int __default_short_cmp(const void* a, const void* b) { + short x = *(short*)a, y = *(short*)b; + if (x < y) return -1; + if (x > y) return 1; + return 0; +} +_UTILITY_STATIC int __default_int_cmp(const void* a, const void* b) { + int x = *(int*)a, y = *(int*)b; + if (x < y) return -1; + if (x > y) return 1; + return 0; +} +_UTILITY_STATIC int __default_long_long_cmp(const void* a, const void* b) { + long long x = *(long long*)a, y = *(long long*)b; + if (x < y) return -1; + if (x > y) return 1; + return 0; +} +_UTILITY_STATIC int __default_long_cmp(const void* a, const void* b) { + long x = *(long*)a, y = *(long*)b; + if (x < y) return -1; + if (x > y) return 1; + return 0; +} + +// cope +#pragma GCC diagnostic ignored "-Woverride-init" // Is is meant to override it on different platforms +const qsort_cmp_t __qsort_cmps[] = { + [sizeof(char)] = __default_char_cmp, + [sizeof(short)] = __default_short_cmp, + [sizeof(int)] = __default_int_cmp, + [sizeof(long)] = __default_long_cmp, + [sizeof(long long)] = __default_long_long_cmp, + [64] = 0, +}; +#pragma GCC diagnostic warning "-Woverride-init" + +#endif // UTILITY_IMPLEMENTATION + + +#endif //JUSTANOTHERCATGIRL_HEADERS_UTILITY diff --git a/tests/dynarray.c b/tests/dynarray.c new file mode 100644 index 0000000..e050d07 --- /dev/null +++ b/tests/dynarray.c @@ -0,0 +1,82 @@ +#define DYNARRAY_IMPLEMENTATION +#define UTILITY_IMPLEMENTATION +#define UTILTY_EXPOSE_HELPERS +#include "../include/dynarray.h" +#include "../include/utility.h" +#include <stdio.h> +#include <stdlib.h> + +/// To test for different types +#if defined(TT_LONG) + #define TEST_TYPE long +#elif defined(TT_INT) + #define TEST_TYPE int +#elif defined(TT_SHORT) + #define TEST_TYPE short +#elif defined(TT_CHAR) + #define TEST_TYPE char +#endif + +void print_arr(TEST_TYPE *a, int line, const char* message) +{ + printf("%20s@%i: %-16s: ", __FILE__, line, message); + printf("%p: {", (void*)a); + for (size_t i = 0UL; i < array_size(a); ++i) { +#if defined(TT_CHAR) + printf("%hhi, ", a[i]); +#elif defined(TT_SHORT) + printf("%hi, ", a[i]); +#elif defined(TT_INT) + printf("%i, ", a[i]); +#elif defined(TT_LONG) + printf("%li, ", a[i]); +#else +#pragma message error "PROVIDE A VALID SIGNED INTEGRAL TYPE" +#endif + } + printf("} size: %ld, cap: %ld, element_size: %ld\n", + array_size(a), array_capacity(a), array_element_size(a) + ); +} + +int main() +{ + TEST_TYPE *arr = array_new(TEST_TYPE, 10); // size: 10, cap: 10 + for (size_t i = 0; i < array_size(arr); ++i) { arr[i] = 127 - 2 * i; } + print_arr(arr, __LINE__, "new array"); + array_resize(arr, 21); // size: 21, cap: 32 + print_arr(arr, __LINE__, "resize more"); + array_resize(arr, 3); // size: 3, cap: 4 + print_arr(arr, __LINE__, "resize less"); + array_push(arr, 42); // size: 4, cap: 4 + array_push(arr, 69); // size: 5, cap: 8 + print_arr(arr, __LINE__, "pushes"); + array_shrink(arr); // size: 5: cap: 5 + print_arr(arr, __LINE__, "shrinking"); + array_qsort_integral(arr); // size: 5, cap: 5 + /* qsort(arr, array_size(arr), array_element_size(arr), __default_int_cmp); */ + print_arr(arr, __LINE__, "qsort"); + array_insert(arr, 20, 1); // size: 6, cap: 8 + print_arr(arr, __LINE__, "1 insert"); + + for (size_t i = 0; i <= array_size(arr); i += 2) { + TEST_TYPE val = 0; // This is crucial. see comment above array_insert declaration. + array_insert(arr, val, i); + } // size: 13, cap: 16 + print_arr(arr, __LINE__, "multiple inserts"); + + TEST_TYPE test_arr_data[] = {0, 42, 0, 20, 0, 69, 0, 123, 0, 125, 0, 127, 0, }; + TEST_TYPE* test_arr = array_new_buffer_copy(TEST_TYPE, test_arr_data, sizeof(test_arr_data) / sizeof(test_arr_data[0])); + typeof(EXIT_SUCCESS) exit_status; + if (array_compare(test_arr, arr, __qsort_cmps[sizeof(TEST_TYPE)])) { + puts("\x1b[31mTest for \"" stringify(TEST_TYPE)"\" failed\x1b[0m"); + exit_status = EXIT_FAILURE; + } else { + puts("\x1b[32mTest for \"" stringify(TEST_TYPE)"\" passed\x1b[0m"); + exit_status = EXIT_SUCCESS; + } + + array_free(arr); + array_free(test_arr); + exit(exit_status); +} |