libc.obj: Add allocator

just add allocator instead of `_ksys_alloc`, `_ksys_free` and `_ksys_realloc`.
This commit is contained in:
2026-02-22 12:42:48 +05:00
parent 668fd4deeb
commit 1cfcfaf627
9 changed files with 669 additions and 14 deletions

View File

@@ -0,0 +1,4 @@
.tup
*.o
*.obj
*.kex

View File

@@ -27,7 +27,8 @@ BIN = \
libc_test.kex \
pipe.kex \
defgen.kex \
futex.kex
futex.kek \
malloc_test.kex
all: $(BIN)

View File

@@ -23,5 +23,6 @@ cp clayer/logo.png /tmp0/1/tcc_samples/logo.png
../tcc defgen.c -o /tmp0/1/tcc_samples/defgen
../tcc pipe.c -o /tmp0/1/tcc_samples/pipe
../tcc futex.c -o /tmp0/1/tcc_samples/futex
../tcc malloc_test.c -o /tmp0/1/tcc_samples/malloc_test
"/sys/File managers/Eolite" /tmp0/1/tcc_samples
exit

View File

@@ -0,0 +1,292 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "../source/stdlib/_mem.h"
#define RUN_TEST(func) \
printf("---\tRUN TEST: %s\t---\n", #func); \
if (func()) { \
printf("[SUCCESS]\tTest %s is ok.\n\n", #func); \
} else { \
printf("[FAIL]\tTest %s failed.\n\n", #func); \
exit(EXIT_FAILURE); \
}
// c between a and b
#define IN_RANGE(a, b, c, len) ((a > c && c > b) || ((a > c + len && c + len > b)))
bool test_malloc_basic_allocation()
{
void* ptr = malloc(sizeof(int));
if (ptr)
free(ptr);
return ptr;
}
bool test_malloc_zero_bytes()
{
return malloc(0) == NULL;
}
bool test_malloc_multiple_allocations()
{
void* ptr[512];
for (int i = 1; i < sizeof(ptr) / sizeof(*ptr); i++) {
ptr[i] = malloc(i);
if (ptr[i] == NULL) {
return false;
}
}
for (int i = 1; i < sizeof(ptr) / sizeof(*ptr); i++) {
for (int j = 1; j < sizeof(ptr) / sizeof(*ptr); j++) {
if (i != j) {
if (ptr[i] == ptr[j]) {
printf("ptrs[%d] == ptrs[%d].\n", i, j);
return false;
} else if (IN_RANGE(
(char*)GET_MEM_NODE_HEADER(ptr[i]) + GET_MEM_NODE_HEADER(ptr[i])->size,
(char*)GET_MEM_NODE_HEADER(ptr[i]),
(char*)GET_MEM_NODE_HEADER(ptr[j]),
GET_MEM_NODE_HEADER(ptr[j])->size)) {
printf("node %p in node %p", GET_MEM_NODE_HEADER(ptr[i]), GET_MEM_NODE_HEADER(ptr[j]));
// additional info, may help with debug
printf("node %p\n size:%p\n free:%p\n next: %p\n last: %p\n", GET_MEM_NODE_HEADER(ptr[i]), GET_MEM_NODE_HEADER(ptr[i])->size, GET_MEM_NODE_HEADER(ptr[i])->free, GET_MEM_NODE_HEADER(ptr[i])->next, GET_MEM_NODE_HEADER(ptr[i])->last);
printf("node %p\n size:%p\n free:%p\n next: %p\n last: %p\n", GET_MEM_NODE_HEADER(ptr[j]), GET_MEM_NODE_HEADER(ptr[j])->size, GET_MEM_NODE_HEADER(ptr[j])->free, GET_MEM_NODE_HEADER(ptr[j])->next, GET_MEM_NODE_HEADER(ptr[j])->last);
exit(EXIT_FAILURE);
}
}
}
}
for (int i = 1; i < sizeof(ptr) / sizeof(*ptr); i++) {
free(ptr[i]);
}
return true;
}
bool test_malloc_data_integrity()
{
const char* As = "AAA";
const char* Cs = "CCC";
char* A = (char*)malloc(10);
char* B = (char*)malloc(10);
char* C = (char*)malloc(10);
if (!A || !B || !C) {
printf("can't alloc\n");
free(A);
free(B);
free(C);
return false;
}
strcpy(A, As);
strcpy(C, Cs);
free(B);
if (strcmp(A, As) != 0) {
printf("A data is broken after free(B). A = '%s'\n", A);
free(A);
free(C);
return false;
}
if (strcmp(C, Cs) != 0) {
printf("C data is broken after free(B). C = '%s'\n", C);
free(A);
free(C);
return false;
}
free(A);
free(C);
return true;
}
bool test_malloc_large_allocation()
{
void* ptr = malloc(1024 * 1024 * 16); // alloc 16mb
if (ptr)
free(ptr);
return ptr;
}
bool test_malloc_allocation_and_free()
{
free(malloc(sizeof(int)));
return true;
}
void fill_buffer(void* ptr, size_t size, unsigned char pattern)
{
if (ptr) {
memset(ptr, pattern, size);
}
}
bool check_buffer(void* ptr, size_t size, unsigned char pattern)
{
if (!ptr) {
return false; // Нельзя проверить NULL
}
unsigned char* byte_ptr = (unsigned char*)ptr;
for (size_t i = 0; i < size; ++i) {
if (byte_ptr[i] != pattern) {
fprintf(stderr, "Ошибка: Байт %zu не соответствует паттерну. Ожидалось %02X, получено %02X\n",
i, pattern, byte_ptr[i]);
return false;
}
}
return true;
}
bool test_realloc_basic_grow()
{
size_t old_size = 10;
size_t new_size = 20;
int* ptr = (int*)malloc(old_size * sizeof(int));
if (ptr == NULL) {
return false;
}
fill_buffer(ptr, old_size * sizeof(int), 0xAA);
int* new_ptr = (int*)realloc(ptr, new_size * sizeof(int));
if (new_ptr == NULL) {
free(ptr);
return false;
}
if (!check_buffer(new_ptr, old_size * sizeof(int), 0xAA)) {
free(new_ptr);
return false;
}
fill_buffer(new_ptr + old_size, (new_size - old_size) * sizeof(int), 0xBB);
if (!check_buffer(new_ptr + old_size, (new_size - old_size) * sizeof(int), 0xBB)) {
free(new_ptr);
return false;
}
free(new_ptr);
return true;
}
bool test_realloc_basic_shrink()
{
size_t old_size = 20;
size_t new_size = 10;
int* ptr = (int*)malloc(old_size * sizeof(int));
if (ptr == NULL) {
return false;
}
fill_buffer(ptr, old_size * sizeof(int), 0xCC);
int* new_ptr = (int*)realloc(ptr, new_size * sizeof(int));
if (new_ptr == NULL) {
free(ptr);
return false;
}
if (!check_buffer(new_ptr, new_size * sizeof(int), 0xCC)) {
free(new_ptr);
return false;
}
free(new_ptr);
return true;
}
bool test_realloc_same_size()
{
size_t size = 15;
int* ptr = (int*)malloc(size * sizeof(int));
if (ptr == NULL) {
return false;
}
fill_buffer(ptr, size * sizeof(int), 0xDD);
int* new_ptr = (int*)realloc(ptr, size * sizeof(int));
if (new_ptr == NULL) {
free(ptr);
return false;
}
if (!check_buffer(new_ptr, size * sizeof(int), 0xDD)) {
free(new_ptr);
return false;
}
free(new_ptr);
return true;
}
bool test_realloc_null_ptr()
{
size_t size = 25;
void* ptr = realloc(NULL, size);
if (ptr == NULL) {
return false;
}
fill_buffer(ptr, size, 0xEE);
if (!check_buffer(ptr, size, 0xEE)) {
free(ptr);
return false;
}
free(ptr);
return true;
}
bool test_realloc_to_zero_size()
{
size_t old_size = 30;
void* ptr = malloc(old_size);
if (ptr == NULL) {
return false;
}
fill_buffer(ptr, old_size, 0xFF);
void* new_ptr = realloc(ptr, 0);
if (new_ptr == NULL) {
printf("realloc(ptr, 0) return NULL.\n");
free(ptr);
} else {
printf("realloc(ptr, 0) return: %p.\n", new_ptr);
free(new_ptr);
}
return true;
}
int main()
{
RUN_TEST(test_malloc_basic_allocation);
RUN_TEST(test_malloc_zero_bytes);
RUN_TEST(test_malloc_multiple_allocations);
RUN_TEST(test_malloc_data_integrity);
RUN_TEST(test_malloc_large_allocation);
RUN_TEST(test_malloc_basic_allocation);
RUN_TEST(test_malloc_allocation_and_free);
RUN_TEST(test_realloc_basic_grow);
RUN_TEST(test_realloc_basic_shrink);
RUN_TEST(test_realloc_same_size);
RUN_TEST(test_realloc_null_ptr);
RUN_TEST(test_realloc_to_zero_size);
return 0;
}

View File

@@ -0,0 +1,81 @@
#ifndef _LIBC_STDLIB__MEM_
#define _LIBC_STDLIB__MEM_
#include <stddef.h>
struct mem_node {
size_t free; // Amount of free space in this node. When equal to size, the entire node is free.
size_t size; // Total size of this memory node.
struct mem_node* last; // Pointer to the previous memory node in the linked list.
struct mem_node* next; // Pointer to the next memory node in the linked list.
};
struct mem_block {
size_t size; // Size of the allocated memory block.
size_t a; // align to 8bytes
};
// Size of the blocks allocated by `_ksys_alloc`
#define ALLOC_BLOCK_SIZE 4096
// Macro to get a pointer to the user data area from a mem_node pointer.
// This is done by adding the size of the mem_node structure to the mem_node pointer.
#define GET_MEM_NODE_PTR(node) (char*)((char*)(node) + sizeof(struct mem_node))
// Macro to check if a memory node is completely free.
#define MEM_NODE_IS_FREE(node) (node->free == node->size)
// Macro to get the amount of used memory in a memory node.
#define GET_MEM_NODE_USED_MEM(node) (node->size - node->free)
// Macro to get a pointer to the mem_node structure from a user data pointer.
// This is done by subtracting the size of the mem_node structure from the user data pointer.
#define GET_MEM_NODE_HEADER(ptr) ((struct mem_node*)(((char*)ptr) - sizeof(struct mem_node)))
// Macro to check if two adjacent memory nodes are in the same block.
// Checks if the end of the left node's allocated space is the start of the right node.
#define MEM_NODES_ARE_IN_ONE_BLOCK(left, right) (GET_MEM_NODE_PTR(left) + ((struct mem_node*)left)->size == (char*)right)
// Macro to merge two adjacent memory nodes.
#define CHECK_SIDE_IN_OTHER_BLOCK(node, side) (side == NULL || ((side != NULL) && !MEM_NODES_ARE_IN_ONE_BLOCK(node, side)))
// align a value to a specified alignment.
// Ensures that the allocated memory is aligned to a certain boundary
inline size_t __mem_align(size_t value, size_t align)
{
return ((value + align - 1) & ~(align - 1));
}
#define __mem_default_align(value) __mem_align(value, 8)
inline struct mem_node* __mem_MERGE_MEM_NODES(struct mem_node* base, struct mem_node* addition)
{
// addition is free && nodes base and addition both in one block, else merge is impossible
if (MEM_NODE_IS_FREE(addition) && MEM_NODES_ARE_IN_ONE_BLOCK(base, addition)) {
// just change size
const size_t s = addition->size + sizeof(struct mem_node);
base->size += s;
base->free += s;
// and delete addition from list
if (addition->next != NULL) {
addition->next->last = base;
base->next = addition->next;
} else {
base->next = NULL;
}
return base;
}
return NULL;
}
// Static pointer to the first memory node in the linked list.
// This acts as the head of the memory pool.
static struct mem_node* __mem_node = NULL;
static struct mem_node* __last_biggest_mem_node = NULL;
#endif // _LIBC_STDLIB_MEM_

View File

@@ -1,14 +1,10 @@
#include <errno.h>
#include <stdlib.h>
#include <sys/ksys.h>
void* calloc(size_t num, size_t size)
{
void* ptr = _ksys_alloc(num * size);
if (!ptr) {
__errno = ENOMEM;
return NULL;
void* ptr = malloc(num * size);
if (ptr) {
memset(ptr, 0, num * size);
}
memset(ptr, 0, num * size);
return ptr;
}

View File

@@ -1,7 +1,97 @@
#include <stdlib.h>
#include <stdbool.h>
#include <sys/ksys.h>
#include "_mem.h"
void free(void* ptr)
{
_ksys_free(ptr);
}
// Handle NULL pointer.
if (ptr == NULL)
return;
// Get a pointer to the mem_node header from the user data pointer.
struct mem_node* node = GET_MEM_NODE_HEADER(ptr);
// Mark the memory node as free.
node->free = node->size;
if (__last_biggest_mem_node == node) {
if (node->last) {
__last_biggest_mem_node = node->last; // anyway node will be merged with next
// and last and last will have size = last + node + next(if its free too)
}
}
// Merge with the next node if possible.
if (node->next != NULL)
__mem_MERGE_MEM_NODES(node, node->next);
// Merge with the previous node if possible.
if (node->last != NULL)
node = __mem_MERGE_MEM_NODES(node->last, node);
if (node) {
// If the current node is not adjacent to either the next or previous node,
// it might be a separate block that can be freed.
if (MEM_NODE_IS_FREE(node) // check it because node maybe was merged with last
&& (node->last == NULL || !MEM_NODES_ARE_IN_ONE_BLOCK(node, node->next))
&& (node->next == NULL || !MEM_NODES_ARE_IN_ONE_BLOCK(node->last, node))) {
// Get a pointer to the mem_block header from the mem_node header.
struct mem_block* block = (struct mem_block*)(((char*)node) - sizeof(struct mem_block));
// Check if the block size matches the node size.
if (block->size == node->size + sizeof(struct mem_block) + sizeof(struct mem_node)) {
// Update the linked list pointers to remove the current node.
if (node->last != NULL)
node->last->next = node->next;
if (node->next != NULL)
node->next->last = node->last;
// Update the head of the linked list if necessary.
if (__mem_node == node) {
if (node->last != NULL) {
__mem_node = node->last;
} else if (node->next != NULL) {
__mem_node = node->next;
} else {
__mem_node = NULL;
}
}
struct mem_node* a = node->next;
struct mem_node* b = node->last;
if (!a && !b) {
__last_biggest_mem_node = NULL;
} else if (a && !b) {
__last_biggest_mem_node = a;
} else if (!a && b) {
__last_biggest_mem_node = b;
} else if (a && b) {
__last_biggest_mem_node = (a->free > b->free) ? a : b;
}
if (__last_biggest_mem_node == node) {
if (node->next && !(node->last)) {
__last_biggest_mem_node = node->next;
} else if (node->last && !(node->next)) {
__last_biggest_mem_node = node->last;
} else if (node->next && node->last) {
if (node->last->free > node->next->free) {
__last_biggest_mem_node = node->last;
} else {
__last_biggest_mem_node = node->next;
}
}
}
// Free the memory block using the ksys_free function.
_ksys_free(block);
}
}
}
}

View File

@@ -1,7 +1,128 @@
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ksys.h>
#include <stdbool.h>
#include "_mem.h"
static struct mem_node* __new_mem_node_from_exist(struct mem_node* current_node, size_t size, bool* from_empty_node)
{
struct mem_node* new_node = NULL;
// Check if the current node has enough free space for the requested size.
if (size + sizeof(struct mem_node) <= current_node->free) {
*from_empty_node = MEM_NODE_IS_FREE(current_node);
if (*from_empty_node) {
new_node = current_node;
} else {
// Calculate the used memory in current node
const size_t s = GET_MEM_NODE_USED_MEM(current_node);
// Create a new memory node after the current node's used space.
new_node = (struct mem_node*)(GET_MEM_NODE_PTR(current_node) + s);
// Update current node's size
current_node->size = s;
// Set the size of the new node.
// for new node give all free space in current node
new_node->size = current_node->free - sizeof(struct mem_node);
// Mark current node as used.
current_node->free = 0;
}
}
return new_node;
}
void* malloc(size_t size)
{
return _ksys_alloc(size);
}
char b[32];
// Handle zero-size allocation.
if (size == 0) {
return NULL;
}
// Align the size to 8 bytes.
size = __mem_default_align(size);
struct mem_node* current_node = __mem_node;
struct mem_node* new_node = NULL; // Pointer to the new node that will be created.
bool from_empty_node = false;
if (__last_biggest_mem_node != NULL)
new_node = __new_mem_node_from_exist(__last_biggest_mem_node, size, &from_empty_node); // try find free space in last created node
// if cant find in __last_biggest_mem_node
if (new_node == NULL) {
// Iterate through the linked list of memory nodes.
while (current_node != NULL) {
new_node = __new_mem_node_from_exist(current_node, size, &from_empty_node);
if (new_node)
break; // Found a suitable node, exit the loop.
current_node = current_node->next; // Move to the next node in the list.
}
}
// If no suitable node was found in the existing list:
if (new_node == NULL) {
// Calculate the size of the new block, including the mem_block header, mem_node header and alignment.
const size_t s = __mem_align(size + sizeof(struct mem_block) + sizeof(struct mem_node), ALLOC_BLOCK_SIZE);
// Allocate a new block of memory using the ksys_alloc function (presumably a kernel-level allocation function).
struct mem_block* block = (struct mem_block*)_ksys_alloc(s);
// Check if the allocation was successful.
if (block == NULL) {
__errno = ENOMEM; // Set the error number to indicate memory allocation failure.
return NULL; // Return NULL to indicate allocation failure.
}
block->size = s;
// Create a new memory node after the mem_block header.
new_node = (struct mem_node*)(((char*)block) + sizeof(struct mem_block));
// Set the size of the new node.
new_node->size = s - sizeof(struct mem_block) - sizeof(struct mem_node);
}
// Set the free space in the new node.
new_node->free = new_node->size - size;
if (!from_empty_node) {
// Set the last pointer of the new node to the current node.
new_node->last = current_node;
// Link the new node into the linked list.
if (current_node != NULL) {
// Set the next pointer of the current node to the new node.
new_node->next = current_node->next;
// Update the last pointer of the next node, if it exists.
if (current_node->next != NULL) {
current_node->next->last = new_node;
}
current_node->next = new_node;
} else {
// If the current node is NULL, the new node is the first node in the list.
new_node->next = NULL;
}
}
// If the linked list was empty, set the head to the new node.
if (__mem_node == NULL) {
__mem_node = new_node;
}
if (__last_biggest_mem_node == NULL || new_node->free > __last_biggest_mem_node->free) {
__last_biggest_mem_node = new_node;
}
// Return a pointer to the user data area of the new node.
return GET_MEM_NODE_PTR(new_node);
}
#undef __mem_align

View File

@@ -1,7 +1,76 @@
#include <stdlib.h>
#include <string.h>
#include <sys/ksys.h>
#include "_mem.h"
// realloc mem. using if other ways not working
static void* fail_realloc(void* ptr, size_t newsize)
{
// Allocate a new block of memory with the new size.
void* new_ptr = malloc(newsize);
// If both the old pointer and the new pointer are not NULL:
if (ptr != NULL && new_ptr != NULL) {
// Copy the data from the old block to the new block.
memcpy(new_ptr, ptr, min(newsize, GET_MEM_NODE_USED_MEM(GET_MEM_NODE_HEADER(ptr))));
}
if (ptr) {
free(ptr); // Free the old block.
}
return new_ptr;
}
void* realloc(void* ptr, size_t newsize)
{
return _ksys_realloc(ptr, newsize);
}
void* new_ptr = NULL;
struct mem_node* node = NULL;
if (ptr && newsize) {
// Get a pointer to the mem_node header from the user data pointer.
node = GET_MEM_NODE_HEADER(ptr);
newsize = __mem_default_align(newsize);
if (node->size >= newsize) { // current node have enough mem
// it work always if newsize is smaller
// Update the free space in the current node.
node->free = node->size - newsize;
// Return the original pointer.
new_ptr = ptr;
} else if (node->last && MEM_NODE_IS_FREE(node->last) && node->size + node->last->size >= newsize) {
// So what happens here is that the node merges with the last node if their volume is sufficient.
// And a reallock is called in the hopes that the first condition will be met.
// if merge failed realloc anyway return pointer, but it will got from `fail_realloc`
struct mem_node* l = node->last;
l = __mem_MERGE_MEM_NODES(l, node);
if (l) {
memmove(GET_MEM_NODE_PTR(l), ptr, GET_MEM_NODE_USED_MEM(node));
new_ptr = realloc(GET_MEM_NODE_PTR(l), newsize);
}
}
}
if (new_ptr == NULL) {
// Allocate a new block of memory with the new size.
new_ptr = malloc(newsize);
// If both the old pointer and the new pointer are not NULL:
if (ptr != NULL && new_ptr != NULL) {
// Copy the data from the old block to the new block.
memcpy(new_ptr, ptr, min(newsize, GET_MEM_NODE_USED_MEM(GET_MEM_NODE_HEADER(ptr))));
}
if (ptr) {
free(ptr); // Free the old block.
}
}
// Return the new pointer.
return new_ptr;
}