diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5475a73 --- /dev/null +++ b/.clang-format @@ -0,0 +1,22 @@ +AlignAfterOpenBracket: DontAlign +AlignEscapedNewlines: DontAlign +AlignOperands: DontAlign +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: TopLevel +BreakBeforeTernaryOperators: false +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: WebKit +IndentCaseLabels: false +TabWidth: 4 +IndentWidth: 4 +ContinuationIndentWidth: 4 +UseTab: ForContinuationAndIndentation +ColumnLimit: 0 +ReflowComments: false +SortIncludes: false +SpaceBeforeParens: false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dd29b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/ \ No newline at end of file diff --git a/README.md b/README.md index ce9d79c..eb39f4b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # ni-arena -Arena Allocator of the NI-C system \ No newline at end of file +Arena Allocator of the NI-C system + +## Dependencies + +- [ni-const](https://codecave.studiosi.es/studiosi/ni-const) constants library of the NI-C system \ No newline at end of file diff --git a/makefile b/makefile new file mode 100644 index 0000000..da61b9f --- /dev/null +++ b/makefile @@ -0,0 +1,27 @@ +RELEASE_flags=-DNDEBUG -O2 -g0 +DEBUG_flags=-std=c23 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og + +clean: + rm -fr bin + +format: + clang-format -i test/niarena_test.c + clang-format -i test/niarena_test_alloc_arena.c + clang-format -i test/niarena_test_alloc_buffer.c + clang-format -i niarena.h + +test: bin/test + bin/niarena_test + bin/niarena_test_alloc_arena + bin/niarena_test_alloc_buffer + +leaks: bin/test + leaks --atExit -- bin/niarena_test + +.PHONY: clean format test + +bin/test: test/niarena_test.c + mkdir -p bin/test + cc ${DEBUG_flags} test/niarena_test.c -o bin/niarena_test + cc ${DEBUG_flags} test/niarena_test_alloc_buffer.c -o bin/niarena_test_alloc_buffer + cc ${DEBUG_flags} test/niarena_test_alloc_buffer.c -o bin/niarena_test_alloc_arena \ No newline at end of file diff --git a/niarena.h b/niarena.h new file mode 100644 index 0000000..376cc1b --- /dev/null +++ b/niarena.h @@ -0,0 +1,94 @@ +#pragma once + +#include "../ni-const/niconst.h" + +// If custom memory management is provided, including this +// header can be skipped +#ifndef NIAREA_NO_STDLIB +#include +#endif + +// The size can be defined by overriding this constant +#ifndef NIARENA_ARENA_SIZE +#define NIARENA_ARENA_SIZE NICONST_MIB_B(4) +#endif + +// Custom memory management can be defined as well. +// Either both or none must be defined +#if (!defined(NIARENA_MALLOC) && defined(NIARENA_FREE)) || (defined(NIARENA_MALLOC) && !defined(NIARENA_FREE)) +#error "Either both NIARENA_MALLOC and NIARENA_FREE have to be defined or neither of them" +#endif +#ifndef NIARENA_MALLOC +#define NIARENA_MALLOC(sz) malloc(sz) +#endif +#ifndef NIARENA_FREE +#define NIARENA_FREE(p) free(p) +#endif + +// Definitions +typedef struct _NIArena { + void *buffer; + size_t capacity; + size_t offset; +} NIArena; + +const char *niarena_get_error(void); +NIArena *niarena_new(void); +void *niarena_alloc(NIArena *arena, size_t size); +void niarena_delete(NIArena *arena); + +#ifdef NIARENA_IMPLEMENTATION + +static const char *niarena__error_reason = ""; + +static void +niarena_error(const char *reason) +{ + niarena__error_reason = reason; +} + +const char * +niarena_get_error(void) +{ + return niarena__error_reason; +} + +NIArena * +niarena_new(void) +{ + NIArena *arena = NIARENA_MALLOC(sizeof(NIArena)); + if(arena == NULL) { + niarena_error("niarena_new: out of memory allocating arena"); + return NULL; + } + arena->buffer = NIARENA_MALLOC(NIARENA_ARENA_SIZE); + if(arena->buffer == NULL) { + NIARENA_FREE(arena); + niarena_error("niarena_new: out of memory allocating buffer"); + return NULL; + } + arena->capacity = NIARENA_ARENA_SIZE; + return arena; +} + +void * +niarena_alloc(NIArena *arena, size_t size) +{ + if((arena->capacity - arena->offset) < size) { + // The allocation is invalid, but the contents of the arena + // can still be used (previous allocations). + niarena_error("niarena_alloc: out of memory inside of the arena"); + return NULL; + } + arena->offset += size; + return (void *)((char *)arena->buffer + arena->offset); +} + +void +niarena_delete(NIArena *arena) +{ + NIARENA_FREE(arena->buffer); + NIARENA_FREE(arena); +} + +#endif diff --git a/test/niarena_test.c b/test/niarena_test.c new file mode 100644 index 0000000..87ae76b --- /dev/null +++ b/test/niarena_test.c @@ -0,0 +1,52 @@ +#define N_INTS_TEST 4 +#define NIARENA_ARENA_SIZE (N_INTS_TEST * sizeof(int)) +#define NIARENA_IMPLEMENTATION + +#include "../niarena.h" +#include +#include +#include + +void +test_arena_new() +{ + NIArena *arena = niarena_new(); + assert(arena->offset == 0); + assert(arena->capacity == (N_INTS_TEST * sizeof(int))); + niarena_delete(arena); +} + +void +test_arena_allocations() +{ + NIArena *arena = niarena_new(); + + int *ns[N_INTS_TEST]; + for(int i = 1; i <= N_INTS_TEST + 1; i++) { + int *p = (int *)niarena_alloc(arena, sizeof(int)); + if(i == N_INTS_TEST + 1) { + // Invalid allocation does not invalidate the rest + assert(p == NULL); + const char *err = niarena_get_error(); + assert(strcmp(err, "niarena_alloc: out of memory inside of the arena") == 0); + } else { + assert(arena->offset == i * sizeof(int)); + *p = 1234 + i; + ns[i - 1] = p; + } + } + for(int i = 1; i <= N_INTS_TEST; i++) { + assert(*ns[i - 1] == 1234 + i); + } + + niarena_delete(arena); +} + +int +main() +{ + test_arena_new(); + test_arena_allocations(); + printf("== TESTS SUCCESSFUL ==\n"); + return 0; +} diff --git a/test/niarena_test_alloc_arena.c b/test/niarena_test_alloc_arena.c new file mode 100644 index 0000000..0a4d1da --- /dev/null +++ b/test/niarena_test_alloc_arena.c @@ -0,0 +1,42 @@ +#include +#include + +void * +bad_malloc(size_t sz) +{ + (void)sz; + return NULL; +} +#define NIARENA_MALLOC(sz) bad_malloc(sz) + +void +mock_free(void *p) +{ + free(p); +} +#define NIARENA_FREE(p) mock_free(p) + +#define N_INTS_TEST 4 +#define NIARENA_ARENA_SIZE (N_INTS_TEST * sizeof(int)) +#define NIARENA_IMPLEMENTATION + +#include "../niarena.h" +#include +#include + +void +niarena_test_malloc_fail_arena() +{ + NIArena *arena = niarena_new(); + assert(arena == NULL); + const char *err = niarena_get_error(); + assert(strcmp(err, "niarena_new: out of memory allocating arena") == 0); +} + +int +main() +{ + niarena_test_malloc_fail_arena(); + printf("== TESTS SUCCESSFUL ==\n"); + return 0; +} diff --git a/test/niarena_test_alloc_buffer.c b/test/niarena_test_alloc_buffer.c new file mode 100644 index 0000000..a9f2b8d --- /dev/null +++ b/test/niarena_test_alloc_buffer.c @@ -0,0 +1,46 @@ +#include +#include + +int call_count = 0; +void * +bad_malloc(size_t sz) +{ + call_count++; + if(call_count != 2) { + return malloc(sz); + } + return NULL; +} +#define NIARENA_MALLOC(sz) bad_malloc(sz) + +void +mock_free(void *p) +{ + free(p); +} +#define NIARENA_FREE(p) mock_free(p) + +#define N_INTS_TEST 4 +#define NIARENA_ARENA_SIZE (N_INTS_TEST * sizeof(int)) +#define NIARENA_IMPLEMENTATION + +#include "../niarena.h" +#include +#include + +void +niarena_test_malloc_fail_buffer() +{ + NIArena *arena = niarena_new(); + assert(arena == NULL); + const char *err = niarena_get_error(); + assert(strcmp(err, "niarena_new: out of memory allocating buffer") == 0); +} + +int +main() +{ + niarena_test_malloc_fail_buffer(); + printf("== TESTS SUCCESSFUL ==\n"); + return 0; +}