Compare commits

..

20 Commits

Author SHA1 Message Date
b2bbb85fcc Merge pull request 'refactor-consistency' (#1) from refactor-consistency into main
Reviewed-on: #1
2026-05-13 20:32:20 -06:00
fb832eab06 refactor: tests adapted and succesfully passed 2026-05-13 20:25:55 -06:00
6c91e1f681 refactor: init and destroy, alloc now first ensures cap
its better usability and is better that it ensures capacity in alloc
like that is what is supposed to be like damn, why i did it like that
2026-05-13 20:10:40 -06:00
f981ba92a8 refactor: Arena is opaque, init returns error code
Is to make it more consistent, i need to change also later arraylist the
init function so its also more consistent, next is making alloc ensure
capacity first so no using it first or some shit.
2026-05-13 19:57:49 -06:00
7c7c68381c Update test/CMakeLists.txt 2026-04-23 12:09:34 -06:00
7af0ca7556 Update CMakeLists.txt 2026-04-23 12:09:07 -06:00
945aa9a27f better compatility cmake 2026-04-23 12:00:35 -06:00
8ebaf06d11 changed project name 2026-04-23 11:58:59 -06:00
3d3b8596cc fix: ArenaSizeResult definition 2026-04-11 22:29:01 -06:00
14eef43f8c refactor: Changed SizeResult to ArenaSizeResult 2026-04-11 21:19:16 -06:00
54522fece5 delete: Deleted helper utility funcitons 2026-04-11 21:17:38 -06:00
15b5cc382f test/refactor: changed things for working with more edge cases 2026-04-11 21:15:31 -06:00
d069a108ce test: Added a buch of tests, also fixes :) 2026-04-11 18:21:40 -06:00
6cea0ddf25 test: Added further testing and fixed functions for edge cases 2026-04-11 11:30:04 -06:00
19a6faf40f test: Tested new ArenaPointerDefinition 2026-04-10 21:09:51 -06:00
121261e24b refactor: Modified ArenaPointer
ArenaPointer now contains a reference to the original arena and an
offset instad of a raw address. Change was made to avoid invalidating
addresses and keeping a more consise system. arena_unwrap_pointer was
also included as a helper function.
2026-04-10 20:54:02 -06:00
cf848f2e37 refactor: Changed Result types return sytaxis 2026-04-03 15:57:54 -06:00
a207018511 fix: Moved padding declaration in arena_ensure_capacity
descripcion mas detallada
2026-04-03 12:51:49 -06:00
0c2e819c2b refactor(all): Fixed alloc and moved growing logic to another function
OK, so evrything was a fucknig mess, it still kinda is, AI helped
me to clean my own mess, it really sucks but the fucking debugger
and address sanitizer were not working, also, there were small
small errors so no big deal but still, a bit sad, also changed a
the way i cast from uint8_t to double and int in the tests, may
change that i guess, verything else i might say is good :)
2026-04-03 12:44:01 -06:00
39dfded924 test and rework: Added push test and cahnged arena_push return
So added a little test that just adds 3 ints to an arena, nothing much
but the basics work, also changed arena_push because it only pushed and
didn't returned a ponter (wich is fucking useless) so also fixed that,
everything seems fine now, need more tests.

# Tipos:
# feat, fix, refactor, docs, style, test, chore
2026-03-31 19:15:37 -06:00
5 changed files with 409 additions and 126 deletions

View File

@@ -1,33 +1,34 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(calculator VERSION 1.0 LANGUAGES C) project(arena VERSION 1.0 LANGUAGES C)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
# Export compile_commands.json (para clangd) # Opciones
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) option(ARENA_BUILD_TESTS "Build arena tests" OFF)
option(ARENA_ENABLE_SANITIZERS "Enable sanitizers for tests" ON)
add_compile_options( # Librería
add_library(arena
src/arena.c
)
target_include_directories(arena
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_compile_options(arena PRIVATE
-Wall -Wall
-Wextra -Wextra
-Wpedantic -Wpedantic
) )
include_directories(include)
add_library(arena_lib
src/arena.c
)
add_executable(arena_main src/main.c)
target_link_libraries(arena_main arena_lib)
# ------------------------ # ------------------------
# Testing # Testing
# ------------------------ # ------------------------
if(ARENA_BUILD_TESTS)
enable_testing() enable_testing()
add_subdirectory(test) add_subdirectory(test)
endif()

View File

@@ -6,11 +6,7 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdbool.h> #include <stdbool.h>
typedef struct { typedef struct Arena Arena;
uint8_t *buffer;
size_t capacity;
size_t offset;
} Arena;
typedef enum { typedef enum {
ARENA_OK = 0, ARENA_OK = 0,
@@ -26,7 +22,10 @@ typedef struct {
bool is_valid; bool is_valid;
union { union {
ArenaErr err; ArenaErr err;
uint8_t *address; struct {
Arena *arena;
size_t offset;
};
}; };
} ArenaPointer; } ArenaPointer;
@@ -34,7 +33,7 @@ typedef struct {
bool is_valid; bool is_valid;
union { union {
ArenaErr err; ArenaErr err;
Arena arena; Arena *arena;
}; };
} ArenaResult; } ArenaResult;
@@ -44,19 +43,21 @@ typedef struct {
ArenaErr err; ArenaErr err;
size_t val; size_t val;
}; };
} SizeResult; } ArenaSizeResult;
ArenaResult arena_init(size_t capacity); ArenaErr arena_init(Arena **arena, size_t capacity);
void arena_destroy(Arena *arena); ArenaErr arena_destroy(Arena **arena);
ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment); ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment);
ArenaErr arena_push(Arena *arena, void *data, size_t size, size_t alignment); ArenaPointer arena_push(Arena *arena, void *data, size_t size, size_t alignment);
ArenaErr arena_realloc(Arena *arena, size_t new_capacity); ArenaErr arena_realloc(Arena *arena, size_t new_capacity);
void *arena_unwrap_pointer(ArenaPointer p);
SizeResult align_arena_offset(Arena *arena, size_t alignment); ArenaSizeResult arena_get_align_padding(Arena *arena, size_t alignment);
// can take big sizes but of sourse there is a limit,
// Should be moved to something like general utilities, // its protected against SIZE_MAX but anything that
// i should make one for all my c projects // is big may break it, still, is an edge case that
bool mul_size_t_safe(size_t a, size_t b, size_t *out); // I shouldn't cover.
ArenaErr arena_ensure_capacity(Arena *arena, size_t size, size_t alignment);
#endif // !ARENA_H #endif // !ARENA_H

View File

@@ -5,115 +5,180 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
ArenaResult arena_init(size_t capacity) { struct Arena {
uint8_t *buffer;
size_t capacity;
size_t offset;
};
void *buffer = malloc(capacity);
if (buffer == NULL) { ArenaErr arena_init(Arena **arena, size_t capacity) {
ArenaResult err = { if (arena == NULL) {
.is_valid = false, return ARENA_NULL_ARG;
.err = ARENA_BAD_ALLOC,
};
return err;
} }
if (capacity < 1) { if (capacity < 1) {
ArenaResult err = { return ARENA_INVALID_SIZE;
.is_valid = false,
.err = ARENA_INVALID_SIZE,
};
return err;
} }
Arena new_arena = { Arena *new_arena = malloc(sizeof(Arena));
if (new_arena == NULL) {
return ARENA_BAD_ALLOC;
}
void *buffer = malloc(capacity);
if (buffer == NULL) {
return ARENA_BAD_ALLOC;
}
*new_arena = (Arena) {
.buffer = buffer, .buffer = buffer,
.capacity = capacity, .capacity = capacity,
.offset = 0, .offset = 0,
}; };
ArenaResult val = {
.is_valid = true,
.arena = new_arena,
};
return val; *arena = new_arena;
return ARENA_OK;
} }
void arena_destroy(Arena *arena) { ArenaErr arena_destroy(Arena **arena) {
if (arena == NULL) { if (arena == NULL || *arena == NULL) {
return; return ARENA_NULL_ARG;
} }
arena->offset = 0; free((*arena)->buffer);
arena->capacity = 0; (*arena)->buffer = NULL;
free(arena->buffer); (*arena)->offset = 0;
(*arena)->capacity = 0;
free(*arena);
*arena = NULL;
return ARENA_OK;
} }
ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment) { ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment) {
if (arena == NULL) { if (arena == NULL) {
ArenaPointer err = { return (ArenaPointer) {.is_valid = false, .err = ARENA_NULL_ARG};
.is_valid = false,
.err = ARENA_NULL_ARG,
};
return err;
} }
SizeResult new_offset = align_arena_offset(arena, alignment); if (size < 1) {
if (!new_offset.is_valid) { return (ArenaPointer) {.is_valid = false, .err = ARENA_INVALID_SIZE};
ArenaPointer err = {
.is_valid = false,
.err = new_offset.err,
};
return err;
} }
if (new_offset.val + size >= arena->capacity) { if (alignment < 1) {
ArenaPointer err = { return (ArenaPointer) {.is_valid = false, .err = ARENA_INVALID_ALIGN};
.is_valid = false,
.err = ARENA_OUT_OF_SPACE,
};
return err;
} }
arena->offset = new_offset.val; ArenaSizeResult padding = arena_get_align_padding(arena, alignment);
if (!padding.is_valid) {
return (ArenaPointer) {.is_valid = false, .err = padding.err};
}
ArenaPointer val = { ArenaErr cap_err = arena_ensure_capacity(arena, size, alignment);
.is_valid = true, if(cap_err != ARENA_OK) {
.address = arena->buffer + arena->offset, return (ArenaPointer) {.is_valid = false, .err = ARENA_OUT_OF_SPACE};
}; }
return val;
if (arena->offset + padding.val + size > arena->capacity) {
return (ArenaPointer) {.is_valid = false, .err = ARENA_OUT_OF_SPACE};
}
if (arena->offset > SIZE_MAX - padding.val - size) {
return (ArenaPointer) {.is_valid = false, .err = ARENA_CAPACITY_OVERFLOW};
}
size_t aligned_offset = arena->offset + padding.val;
arena->offset = aligned_offset + size;
return (ArenaPointer) {.is_valid = true, .offset = aligned_offset, .arena = arena};
} }
ArenaErr arena_push(Arena *arena, void *data, size_t size, size_t alignment) { ArenaErr arena_ensure_capacity(Arena *arena, size_t size, size_t alignment) {
if (arena == NULL || data == NULL) { if (arena == NULL) {
return ARENA_NULL_ARG; return ARENA_NULL_ARG;
} }
ArenaPointer pointer = arena_alloc(arena, size, alignment); if (size == 0 || size == SIZE_MAX) {
return ARENA_INVALID_SIZE;
if (!pointer.is_valid) {
return pointer.err;
} }
while (!pointer.is_valid && pointer.err == ARENA_OUT_OF_SPACE) {
if (alignment == 0 || (alignment & (alignment - 1)) != 0) {
return ARENA_INVALID_ALIGN;
}
ArenaSizeResult padding_res = arena_get_align_padding(arena, alignment);
if (!padding_res.is_valid) {
return padding_res.err;
}
size_t padding = padding_res.val;
while (true) {
size_t required;
if (arena->offset > SIZE_MAX - padding) {
return ARENA_CAPACITY_OVERFLOW;
}
required = arena->offset + padding;
if (required > SIZE_MAX - size) {
return ARENA_CAPACITY_OVERFLOW;
}
required += size;
if (required <= arena->capacity) {
return ARENA_OK;
}
size_t new_capacity; size_t new_capacity;
bool is_overflow = mul_size_t_safe(arena->capacity, 2, &new_capacity); if (arena->capacity > SIZE_MAX / 2) {
if (is_overflow) {
return ARENA_CAPACITY_OVERFLOW; return ARENA_CAPACITY_OVERFLOW;
} }
new_capacity = arena->capacity * 2;
if (new_capacity < required) {
new_capacity = required;
}
ArenaErr err = arena_realloc(arena, new_capacity); ArenaErr err = arena_realloc(arena, new_capacity);
if (err != ARENA_OK) { if (err != ARENA_OK) {
return err; return err;
} }
}
}
pointer = arena_alloc(arena, size, alignment); ArenaPointer arena_push(Arena *arena, void *data, size_t size, size_t alignment) {
if (arena == NULL || data == NULL) {
return (ArenaPointer) {.is_valid = false, .err = ARENA_NULL_ARG};
} }
if (size < 1) {
return (ArenaPointer) {.is_valid = false, .err = ARENA_INVALID_SIZE};
}
if (size < 1) {
return (ArenaPointer) {.is_valid = false, .err = ARENA_INVALID_ALIGN};
}
ArenaErr err = arena_ensure_capacity(arena, size, alignment);
if (err != ARENA_OK) {
return (ArenaPointer) {.is_valid = false, .err = err};
}
ArenaPointer pointer = arena_alloc(arena, size, alignment);
if (!pointer.is_valid) {
return pointer;
}
memcpy( memcpy(
pointer.address, arena_unwrap_pointer(pointer),
data, data,
size); size);
return ARENA_OK; return pointer;
} }
ArenaErr arena_realloc(Arena *arena, size_t new_capacity) { ArenaErr arena_realloc(Arena *arena, size_t new_capacity) {
@@ -121,6 +186,10 @@ ArenaErr arena_realloc(Arena *arena, size_t new_capacity) {
return ARENA_NULL_ARG; return ARENA_NULL_ARG;
} }
if (new_capacity < 1) {
return ARENA_INVALID_SIZE;
}
uint8_t *tmp = realloc(arena->buffer, new_capacity); uint8_t *tmp = realloc(arena->buffer, new_capacity);
if (tmp == NULL) { if (tmp == NULL) {
return ARENA_BAD_ALLOC; return ARENA_BAD_ALLOC;
@@ -131,42 +200,33 @@ ArenaErr arena_realloc(Arena *arena, size_t new_capacity) {
return ARENA_OK; return ARENA_OK;
} }
SizeResult align_arena_offset(Arena *arena, size_t alignment) {
ArenaSizeResult arena_get_align_padding(Arena *arena, size_t alignment) {
if (arena == NULL) { if (arena == NULL) {
SizeResult err = { return (ArenaSizeResult){ .is_valid = false, .err = ARENA_NULL_ARG };
.is_valid = false,
.err = ARENA_NULL_ARG,
};
return err;
} }
if (alignment < 1) { if (alignment == 0 || (alignment & (alignment - 1)) != 0) {
SizeResult err = { return (ArenaSizeResult){ .is_valid = false, .err = ARENA_INVALID_ALIGN };
.is_valid = false,
.err = ARENA_INVALID_ALIGN,
};
return err;
} }
SizeResult val = { uintptr_t current_address = (uintptr_t)(arena->buffer + arena->offset);
size_t mask = alignment - 1;
size_t padding = (size_t)(-current_address) & mask;
return (ArenaSizeResult){
.is_valid = true, .is_valid = true,
.val = ((arena->offset + (alignment - 1)) & ~(alignment - 1)), .val = padding
// Formula for alignment, found at stackoverflow, i came up with one
// but it involved 3 divisions, this one is way faster :)
}; };
return val;
} }
bool mul_size_t_safe(size_t a, size_t b, size_t *out) { void *arena_unwrap_pointer(ArenaPointer p) {
if (out == NULL) { if (!p.is_valid) {
return true; return NULL;
} }
return p.arena->buffer + p.offset;
if (a != 0 && b > SIZE_MAX / a) {
return true;
}
*out = a * b;
return false;
} }

View File

@@ -3,8 +3,21 @@ find_package(cmocka REQUIRED)
add_executable(test_arena test_arena.c) add_executable(test_arena test_arena.c)
target_link_libraries(test_arena target_link_libraries(test_arena
arena_lib arena
cmocka::cmocka cmocka::cmocka
) )
# Sanitizers (solo si están activados y el compilador lo soporta)
if(ARENA_ENABLE_SANITIZERS)
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(test_arena PRIVATE
-fsanitize=address
-fno-omit-frame-pointer
)
target_link_options(test_arena PRIVATE
-fsanitize=address
)
endif()
endif()
add_test(NAME arena_tests COMMAND test_arena) add_test(NAME arena_tests COMMAND test_arena)

View File

@@ -1,6 +1,214 @@
#include "arena.h" #include <stdalign.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
#include <string.h>
#include <limits.h>
#include "arena.h"
static void test_push_3_ints(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, sizeof(int) * 3), ARENA_OK);
int int_to_push = 20;
ArenaPointer result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid);
assert_int_equal(20, *(int*)arena_unwrap_pointer(result));
assert_int_equal(20, *(int*)arena_unwrap_pointer(result));
int_to_push = 30;
result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid);
assert_int_equal(30, *(int*)arena_unwrap_pointer(result));
int_to_push = 40;
result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid);
assert_int_equal(40, *(int*)arena_unwrap_pointer(result));
arena_destroy(&arena);
}
static void test_push_3_ints_2_doubles(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(
arena_init(&arena, (sizeof(int) * 3) + (sizeof(double) * 2)),
ARENA_OK
);
int int_to_push = 20;
ArenaPointer result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid);
assert_int_equal(20, *(int*)arena_unwrap_pointer(result));
double double_to_push = 4.57;
result = arena_push(arena, &double_to_push, sizeof(double), alignof(double));
assert_true(result.is_valid);
assert_double_equal(4.57, *(double*)arena_unwrap_pointer(result), 1e-6);
int_to_push = 30;
result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid);
assert_int_equal(30, *(int*)arena_unwrap_pointer(result));
int_to_push = 40;
result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid);
assert_int_equal(40, *(int*)arena_unwrap_pointer(result));
double_to_push = 267.33;
result = arena_push(arena, &double_to_push, sizeof(double), alignof(double));
assert_true(result.is_valid);
assert_double_equal(267.33, *(double*)arena_unwrap_pointer(result), 1e-6);
arena_destroy(&arena);
}
static void test_init_arena_0_cap(void **state) {
(void) state;
Arena *arena;
assert_int_equal(arena_init(&arena, 0), ARENA_INVALID_SIZE);
}
static void test_arena_alloc_size_0(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaPointer pointer = arena_alloc(arena, 0, alignof(int));
assert_false(pointer.is_valid);
assert_int_equal(pointer.err, ARENA_INVALID_SIZE);
arena_destroy(&arena);
}
static void test_arena_alloc_size_max(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaPointer pointer = arena_alloc(arena, SIZE_MAX, alignof(int));
assert_false(pointer.is_valid);
assert_int_equal(pointer.err, ARENA_OUT_OF_SPACE);
arena_destroy(&arena);
}
static void test_arena_alloc_align_0(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaPointer pointer = arena_alloc(arena, sizeof(int), 0);
assert_false(pointer.is_valid);
assert_int_equal(pointer.err, ARENA_INVALID_ALIGN);
arena_destroy(&arena);
}
static void test_arena_alloc_align_max(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaPointer pointer = arena_alloc(arena, sizeof(int), SIZE_MAX);
assert_false(pointer.is_valid);
assert_int_equal(pointer.err, ARENA_INVALID_ALIGN);
arena_destroy(&arena);
}
static void test_arena_align_size_0(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaPointer pointer = arena_alloc(arena, sizeof(int), 0);
assert_false(pointer.is_valid);
assert_int_equal(pointer.err, ARENA_INVALID_ALIGN);
arena_destroy(&arena);
}
static void test_arena_ensure_capacity_size_0(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaErr err = arena_ensure_capacity(arena, 0, alignof(int));
assert_int_equal(err, ARENA_INVALID_SIZE);
arena_destroy(&arena);
}
static void test_arena_ensure_capacity_size_max(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaErr err = arena_ensure_capacity(arena, SIZE_MAX, alignof(int));
assert_int_equal(err, ARENA_INVALID_SIZE);
arena_destroy(&arena);
}
static void test_arena_ensure_capacity_align_0(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaErr err = arena_ensure_capacity(arena, sizeof(int), 0);
assert_int_equal(err, ARENA_INVALID_ALIGN);
arena_destroy(&arena);
}
static void test_arena_ensure_capacity_align_max(void **state) {
(void) state;
Arena *arena;
assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
ArenaErr err = arena_ensure_capacity(arena, sizeof(int), SIZE_MAX);
assert_int_equal(err, ARENA_INVALID_ALIGN);
arena_destroy(&arena);
}
int main(void) { int main(void) {
return EXIT_SUCCESS; const struct CMUnitTest tests[] = {
cmocka_unit_test(test_push_3_ints),
cmocka_unit_test(test_push_3_ints_2_doubles),
cmocka_unit_test(test_init_arena_0_cap),
cmocka_unit_test(test_arena_alloc_size_0),
cmocka_unit_test(test_arena_alloc_size_max),
cmocka_unit_test(test_arena_alloc_align_0),
cmocka_unit_test(test_arena_alloc_align_max),
cmocka_unit_test(test_arena_align_size_0),
cmocka_unit_test(test_arena_ensure_capacity_align_0),
cmocka_unit_test(test_arena_ensure_capacity_align_max),
cmocka_unit_test(test_arena_ensure_capacity_size_0),
cmocka_unit_test(test_arena_ensure_capacity_size_max),
};
return cmocka_run_group_tests(tests, NULL, NULL);
} }