Compare commits

...

12 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
5 changed files with 226 additions and 112 deletions

View File

@@ -1,39 +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)
function(enable_sanitizers target)
target_compile_options(${target} PRIVATE -fsanitize=address -fno-omit-frame-pointer)
target_link_options(${target} PRIVATE -fsanitize=address)
endfunction()
# ------------------------ # ------------------------
# 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,
@@ -37,7 +33,7 @@ typedef struct {
bool is_valid; bool is_valid;
union { union {
ArenaErr err; ArenaErr err;
Arena arena; Arena *arena;
}; };
} ArenaResult; } ArenaResult;
@@ -47,21 +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);
ArenaPointer 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); void *arena_unwrap_pointer(ArenaPointer p);
SizeResult arena_get_align_padding(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,
// its protected against SIZE_MAX but anything that
// is big may break it, still, is an edge case that
// I shouldn't cover.
ArenaErr arena_ensure_capacity(Arena *arena, size_t size, size_t alignment); ArenaErr arena_ensure_capacity(Arena *arena, size_t size, size_t alignment);
// Should be moved to something like general utilities,
// i should make one for all my c projects
bool mul_size_t_safe(size_t a, size_t b, size_t *out);
#endif // !ARENA_H #endif // !ARENA_H

View File

@@ -5,32 +5,56 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
struct Arena {
uint8_t *buffer;
size_t capacity;
size_t offset;
};
ArenaResult arena_init(size_t capacity) { ArenaErr arena_init(Arena **arena, size_t capacity) {
if (arena == NULL) {
return ARENA_NULL_ARG;
}
if (capacity < 1) { if (capacity < 1) {
return (ArenaResult) {.is_valid = false, .err = ARENA_INVALID_SIZE}; return ARENA_INVALID_SIZE;
}
Arena *new_arena = malloc(sizeof(Arena));
if (new_arena == NULL) {
return ARENA_BAD_ALLOC;
} }
void *buffer = malloc(capacity); void *buffer = malloc(capacity);
if (buffer == NULL) { if (buffer == NULL) {
return (ArenaResult) {.is_valid = false, .err = ARENA_BAD_ALLOC}; return ARENA_BAD_ALLOC;
} }
Arena new_arena = { *new_arena = (Arena) {
.buffer = buffer, .buffer = buffer,
.capacity = capacity, .capacity = capacity,
.offset = 0, .offset = 0,
}; };
return (ArenaResult) {.is_valid = true, .arena = new_arena}; *arena = new_arena;
return ARENA_OK;
} }
void arena_destroy(Arena *arena) { ArenaErr arena_destroy(Arena **arena) {
free(arena->buffer); if (arena == NULL || *arena == NULL) {
arena->buffer = NULL; return ARENA_NULL_ARG;
arena->offset = 0; }
arena->capacity = 0;
free((*arena)->buffer);
(*arena)->buffer = NULL;
(*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) {
@@ -46,11 +70,16 @@ ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment) {
return (ArenaPointer) {.is_valid = false, .err = ARENA_INVALID_ALIGN}; return (ArenaPointer) {.is_valid = false, .err = ARENA_INVALID_ALIGN};
} }
SizeResult padding = arena_get_align_padding(arena, alignment); ArenaSizeResult padding = arena_get_align_padding(arena, alignment);
if (!padding.is_valid) { if (!padding.is_valid) {
return (ArenaPointer) {.is_valid = false, .err = padding.err}; return (ArenaPointer) {.is_valid = false, .err = padding.err};
} }
ArenaErr cap_err = arena_ensure_capacity(arena, size, alignment);
if(cap_err != ARENA_OK) {
return (ArenaPointer) {.is_valid = false, .err = ARENA_OUT_OF_SPACE};
}
if (arena->offset + padding.val + size > arena->capacity) { if (arena->offset + padding.val + size > arena->capacity) {
return (ArenaPointer) {.is_valid = false, .err = ARENA_OUT_OF_SPACE}; return (ArenaPointer) {.is_valid = false, .err = ARENA_OUT_OF_SPACE};
} }
@@ -70,34 +99,50 @@ ArenaErr arena_ensure_capacity(Arena *arena, size_t size, size_t alignment) {
return ARENA_NULL_ARG; return ARENA_NULL_ARG;
} }
if (size < 1) { if (size == 0 || size == SIZE_MAX) {
return ARENA_INVALID_SIZE; return ARENA_INVALID_SIZE;
} }
if (alignment < 1) { if (alignment == 0 || (alignment & (alignment - 1)) != 0) {
return ARENA_INVALID_ALIGN; return ARENA_INVALID_ALIGN;
} }
SizeResult padding = arena_get_align_padding(arena, alignment); ArenaSizeResult padding_res = arena_get_align_padding(arena, alignment);
if (!padding.is_valid) { if (!padding_res.is_valid) {
return padding.err; return padding_res.err;
} }
size_t padding = padding_res.val;
while (true) { while (true) {
if (arena->offset > SIZE_MAX - padding.val - size) { size_t required;
if (arena->offset > SIZE_MAX - padding) {
return ARENA_CAPACITY_OVERFLOW; return ARENA_CAPACITY_OVERFLOW;
} }
required = arena->offset + padding;
size_t required = arena->offset + padding.val + size; if (required > SIZE_MAX - size) {
return ARENA_CAPACITY_OVERFLOW;
}
required += size;
if (required <= arena->capacity) { if (required <= arena->capacity) {
return ARENA_OK; return ARENA_OK;
} }
size_t new_capacity; size_t new_capacity;
if (mul_size_t_safe(arena->capacity, 2, &new_capacity)) {
if (arena->capacity > SIZE_MAX / 2) {
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;
@@ -157,19 +202,24 @@ ArenaErr arena_realloc(Arena *arena, size_t new_capacity) {
SizeResult arena_get_align_padding(Arena *arena, size_t alignment) { ArenaSizeResult arena_get_align_padding(Arena *arena, size_t alignment) {
if (arena == NULL) { if (arena == NULL) {
return (SizeResult) {.is_valid = false, .err = ARENA_NULL_ARG}; return (ArenaSizeResult){ .is_valid = false, .err = ARENA_NULL_ARG };
} }
if (alignment < 1) { if (alignment == 0 || (alignment & (alignment - 1)) != 0) {
return (SizeResult) {.is_valid = false, .err = ARENA_INVALID_ALIGN}; return (ArenaSizeResult){ .is_valid = false, .err = ARENA_INVALID_ALIGN };
} }
uintptr_t current_address = (uintptr_t) (arena->buffer + arena->offset); uintptr_t current_address = (uintptr_t)(arena->buffer + arena->offset);
uintptr_t aligned = ((current_address + (alignment - 1)) & ~(alignment - 1));
return (SizeResult) {.is_valid = true, .err = aligned - current_address}; size_t mask = alignment - 1;
size_t padding = (size_t)(-current_address) & mask;
return (ArenaSizeResult){
.is_valid = true,
.val = padding
};
} }
void *arena_unwrap_pointer(ArenaPointer p) { void *arena_unwrap_pointer(ArenaPointer p) {
@@ -179,16 +229,4 @@ void *arena_unwrap_pointer(ArenaPointer p) {
return p.arena->buffer + p.offset; return p.arena->buffer + p.offset;
} }
bool mul_size_t_safe(size_t a, size_t b, size_t *out) {
if (out == NULL) {
return true;
}
if (a != 0 && b > SIZE_MAX / a) {
return true;
}
*out = a * b;
return false;
}

View File

@@ -3,13 +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
) )
enable_sanitizers(test_arena) # Sanitizers (solo si están activados y el compilador lo soporta)
if(ARENA_ENABLE_SANITIZERS)
target_compile_options(test_arena PRIVATE -fsanitize=address -fno-omit-frame-pointer) if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_link_options(test_arena PRIVATE -fsanitize=address) 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

@@ -14,23 +14,22 @@
static void test_push_3_ints(void **state) { static void test_push_3_ints(void **state) {
(void) state; (void) state;
ArenaResult value = arena_init(sizeof(int) * 3); Arena *arena;
assert_true(value.is_valid); assert_uint_equal(arena_init(&arena, sizeof(int) * 3), ARENA_OK);
Arena arena = value.arena;
int int_to_push = 20; int int_to_push = 20;
ArenaPointer result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); ArenaPointer result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid); assert_true(result.is_valid);
assert_int_equal(20, *(int*)arena_unwrap_pointer(result)); assert_int_equal(20, *(int*)arena_unwrap_pointer(result));
assert_int_equal(20, *(int*)arena_unwrap_pointer(result)); assert_int_equal(20, *(int*)arena_unwrap_pointer(result));
int_to_push = 30; int_to_push = 30;
result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid); assert_true(result.is_valid);
assert_int_equal(30, *(int*)arena_unwrap_pointer(result)); assert_int_equal(30, *(int*)arena_unwrap_pointer(result));
int_to_push = 40; int_to_push = 40;
result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid); assert_true(result.is_valid);
assert_int_equal(40, *(int*)arena_unwrap_pointer(result)); assert_int_equal(40, *(int*)arena_unwrap_pointer(result));
@@ -40,32 +39,34 @@ static void test_push_3_ints(void **state) {
static void test_push_3_ints_2_doubles(void **state) { static void test_push_3_ints_2_doubles(void **state) {
(void) state; (void) state;
ArenaResult value = arena_init((sizeof(int) * 3) + (sizeof(double) * 2)); Arena *arena;
assert_true(value.is_valid); assert_uint_equal(
Arena arena = value.arena; arena_init(&arena, (sizeof(int) * 3) + (sizeof(double) * 2)),
ARENA_OK
);
int int_to_push = 20; int int_to_push = 20;
ArenaPointer result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); ArenaPointer result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid); assert_true(result.is_valid);
assert_int_equal(20, *(int*)arena_unwrap_pointer(result)); assert_int_equal(20, *(int*)arena_unwrap_pointer(result));
double double_to_push = 4.57; double double_to_push = 4.57;
result = arena_push(&arena, &double_to_push, sizeof(double), alignof(double)); result = arena_push(arena, &double_to_push, sizeof(double), alignof(double));
assert_true(result.is_valid); assert_true(result.is_valid);
assert_double_equal(4.57, *(double*)arena_unwrap_pointer(result), 1e-6); assert_double_equal(4.57, *(double*)arena_unwrap_pointer(result), 1e-6);
int_to_push = 30; int_to_push = 30;
result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid); assert_true(result.is_valid);
assert_int_equal(30, *(int*)arena_unwrap_pointer(result)); assert_int_equal(30, *(int*)arena_unwrap_pointer(result));
int_to_push = 40; int_to_push = 40;
result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); result = arena_push(arena, &int_to_push, sizeof(int), alignof(int));
assert_true(result.is_valid); assert_true(result.is_valid);
assert_int_equal(40, *(int*)arena_unwrap_pointer(result)); assert_int_equal(40, *(int*)arena_unwrap_pointer(result));
double_to_push = 267.33; double_to_push = 267.33;
result = arena_push(&arena, &double_to_push, sizeof(double), alignof(double)); result = arena_push(arena, &double_to_push, sizeof(double), alignof(double));
assert_true(result.is_valid); assert_true(result.is_valid);
assert_double_equal(267.33, *(double*)arena_unwrap_pointer(result), 1e-6); assert_double_equal(267.33, *(double*)arena_unwrap_pointer(result), 1e-6);
@@ -75,19 +76,17 @@ static void test_push_3_ints_2_doubles(void **state) {
static void test_init_arena_0_cap(void **state) { static void test_init_arena_0_cap(void **state) {
(void) state; (void) state;
ArenaResult arena = arena_init(0); Arena *arena;
assert_false(arena.is_valid); assert_int_equal(arena_init(&arena, 0), ARENA_INVALID_SIZE);
assert_int_equal(arena.err, ARENA_INVALID_SIZE);
} }
static void test_arena_alloc_size_0(void **state) { static void test_arena_alloc_size_0(void **state) {
(void) state; (void) state;
ArenaResult value = arena_init(64); Arena *arena;
assert_true(value.is_valid); assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
Arena arena = value.arena;
ArenaPointer pointer = arena_alloc(&arena, 0, alignof(int)); ArenaPointer pointer = arena_alloc(arena, 0, alignof(int));
assert_false(pointer.is_valid); assert_false(pointer.is_valid);
assert_int_equal(pointer.err, ARENA_INVALID_SIZE); assert_int_equal(pointer.err, ARENA_INVALID_SIZE);
@@ -97,31 +96,103 @@ static void test_arena_alloc_size_0(void **state) {
static void test_arena_alloc_size_max(void **state) { static void test_arena_alloc_size_max(void **state) {
(void) state; (void) state;
ArenaResult value = arena_init(64); Arena *arena;
assert_true(value.is_valid); assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
Arena arena = value.arena;
ArenaPointer pointer = arena_alloc(&arena, SIZE_MAX, alignof(int)); ArenaPointer pointer = arena_alloc(arena, SIZE_MAX, alignof(int));
assert_false(pointer.is_valid); assert_false(pointer.is_valid);
assert_int_equal(pointer.err, ARENA_OUT_OF_SPACE); assert_int_equal(pointer.err, ARENA_OUT_OF_SPACE);
arena_destroy(&arena); 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) { static void test_arena_align_size_0(void **state) {
(void) state; (void) state;
ArenaResult value = arena_init(64); Arena *arena;
assert_true(value.is_valid); assert_uint_equal(arena_init(&arena, 64), ARENA_OK);
Arena arena = value.arena;
ArenaPointer pointer = arena_alloc(&arena, sizeof(int), 0); ArenaPointer pointer = arena_alloc(arena, sizeof(int), 0);
assert_false(pointer.is_valid); assert_false(pointer.is_valid);
assert_int_equal(pointer.err, ARENA_INVALID_ALIGN); assert_int_equal(pointer.err, ARENA_INVALID_ALIGN);
arena_destroy(&arena); 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) {
const struct CMUnitTest tests[] = { const struct CMUnitTest tests[] = {
cmocka_unit_test(test_push_3_ints), cmocka_unit_test(test_push_3_ints),
@@ -129,7 +200,13 @@ int main(void) {
cmocka_unit_test(test_init_arena_0_cap), cmocka_unit_test(test_init_arena_0_cap),
cmocka_unit_test(test_arena_alloc_size_0), cmocka_unit_test(test_arena_alloc_size_0),
cmocka_unit_test(test_arena_alloc_size_max), 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_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),
}; };