ft_malloc/main.c

419 lines
9.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
/*
** match allocators thresholds
** (only used here for shaping size distributions)
*/
#define TINY_MAX 64
#define SMALL_MAX 4096
#define ARRAY_SIZE 10000 // number of live slots we manage
#define RANDOM_ITERS 200000 // number of random operations
#define PATTERN_BASE 0xA5
#define MAX_LARGE_SIZE (SMALL_MAX * 8) // up to 32k-ish
typedef struct s_block
{
void *ptr;
size_t size;
unsigned char pattern;
int is_calloc;
} t_block;
static t_block g_blocks[ARRAY_SIZE];
/* Fill a block with a simple pattern byte */
static void fill_pattern(void *ptr, size_t size, unsigned char pattern)
{
if (!ptr || size == 0)
return;
memset(ptr, pattern, size);
}
/* Check that a block still has the expected pattern */
static int check_pattern(void *ptr, size_t size, unsigned char pattern)
{
if (!ptr || size == 0)
return 1;
unsigned char *p = (unsigned char *)ptr;
for (size_t i = 0; i < size; ++i)
{
if (p[i] != pattern)
{
fprintf(stderr,
"[ERROR] Pattern mismatch at %p (offset %zu): "
"expected 0x%02X, got 0x%02X\n",
ptr, i, (unsigned)pattern, (unsigned)p[i]);
return 0;
}
}
return 1;
}
/* Check that a calloc'd block is zeroed */
static int check_zero(void *ptr, size_t size)
{
if (!ptr || size == 0)
return 1;
unsigned char *p = (unsigned char *)ptr;
for (size_t i = 0; i < size; ++i)
{
if (p[i] != 0)
{
fprintf(stderr,
"[ERROR] Calloc block not zeroed at %p (offset %zu): got 0x%02X\n",
ptr, i, (unsigned)p[i]);
return 0;
}
}
return 1;
}
/* Generate a pseudo-random size skewed across tiny/small/large */
static size_t random_size(void)
{
int r = rand() % 100;
if (r < 40)
{
/* TINY range [1..TINY_MAX] */
return (size_t)(1 + rand() % TINY_MAX);
}
else if (r < 85)
{
/* SMALL range [TINY_MAX+1 .. SMALL_MAX] */
return (size_t)(TINY_MAX + 1 + rand() % (SMALL_MAX - TINY_MAX));
}
else
{
/* LARGE range [SMALL_MAX+1 .. MAX_LARGE_SIZE] */
return (size_t)(SMALL_MAX + 1 + rand() % (MAX_LARGE_SIZE - SMALL_MAX - 1));
}
}
/* Tear down all blocks cleanly (for the end of tests) */
static void free_all_blocks(void)
{
for (int i = 0; i < ARRAY_SIZE; ++i)
{
if (g_blocks[i].ptr)
{
free(g_blocks[i].ptr);
g_blocks[i].ptr = NULL;
g_blocks[i].size = 0;
}
}
}
/* Simple deterministic sanity tests */
static void basic_tests(void)
{
printf("== basic_tests ==\n");
/* TINY */
void *a = malloc(10);
void *b = malloc(TINY_MAX);
fill_pattern(a, 10, 0x11);
fill_pattern(b, TINY_MAX, 0x22);
check_pattern(a, 10, 0x11);
check_pattern(b, TINY_MAX, 0x22);
free(a);
free(b);
/* SMALL */
void *c = malloc(SMALL_MAX / 2);
fill_pattern(c, SMALL_MAX / 2, 0x33);
check_pattern(c, SMALL_MAX / 2, 0x33);
c = realloc(c, SMALL_MAX / 2 + 100); // grow within SMALL
check_pattern(c, SMALL_MAX / 2, 0x33);
free(c);
/* LARGE */
size_t large_sz = SMALL_MAX * 4;
void *d = malloc(large_sz);
fill_pattern(d, large_sz, 0x44);
check_pattern(d, large_sz, 0x44);
d = realloc(d, large_sz * 2); // grow LARGE
check_pattern(d, large_sz, 0x44);
d = realloc(d, large_sz / 2); // shrink LARGE
check_pattern(d, large_sz / 2, 0x44);
free(d);
/* CALLOC */
void *e = calloc(1000, 1);
check_zero(e, 1000);
memset(e, 0xAB, 1000);
e = realloc(e, 2000);
check_pattern(e, 1000, 0xAB);
free(e);
printf("basic_tests: OK\n\n");
}
static void random_stress(void)
{
printf("== random_stress ==\n");
for (int i = 0; i < ARRAY_SIZE; ++i)
{
g_blocks[i].ptr = NULL;
g_blocks[i].size = 0;
g_blocks[i].pattern = 0;
g_blocks[i].is_calloc = 0;
}
for (int iter = 0; iter < RANDOM_ITERS; ++iter)
{
int idx = rand() % ARRAY_SIZE;
t_block *blk = &g_blocks[idx];
int op = rand() % 4; /* 0 = alloc/new, 1 = free, 2 = realloc up, 3 = realloc down */
/* Invariant: for non-NULL blocks we always keep full [0..size) filled with pattern */
if (blk->ptr && !blk->is_calloc)
{
if (!check_pattern(blk->ptr, blk->size, blk->pattern))
{
fprintf(stderr, "[FATAL] pattern mismatch before op; idx=%d\n", idx);
abort();
}
}
if (op == 0)
{
/* allocate new if empty, otherwise grow via realloc */
if (blk->ptr == NULL)
{
size_t sz = random_size();
int use_calloc = (rand() % 4) == 0; /* 25% calloc */
if (use_calloc)
{
blk->ptr = calloc(sz, 1);
blk->is_calloc = 1;
blk->size = sz;
if (!blk->ptr)
{
fprintf(stderr, "[ERROR] calloc returned NULL\n");
continue;
}
/* we only check zero once; then we switch to pattern mode */
if (!check_zero(blk->ptr, blk->size))
fprintf(stderr, "[ERROR] calloc block not zeroed (idx=%d)\n", idx);
blk->pattern = (unsigned char)((PATTERN_BASE + idx) & 0xFF);
fill_pattern(blk->ptr, blk->size, blk->pattern);
blk->is_calloc = 0;
}
else
{
blk->ptr = malloc(sz);
blk->is_calloc = 0;
blk->size = sz;
if (!blk->ptr)
{
fprintf(stderr, "[ERROR] malloc returned NULL\n");
continue;
}
blk->pattern = (unsigned char)((PATTERN_BASE + idx) & 0xFF);
fill_pattern(blk->ptr, blk->size, blk->pattern);
}
}
else
{
/* grow via realloc */
size_t old_size = blk->size;
size_t new_sz = old_size + 1 + (rand() % (old_size + 1));
unsigned char pattern = blk->pattern;
void *new_ptr = realloc(blk->ptr, new_sz);
if (!new_ptr)
{
fprintf(stderr, "[ERROR] realloc (grow) returned NULL\n");
continue; /* keep old block as-is */
}
blk->ptr = new_ptr;
blk->size = new_sz;
blk->pattern = pattern;
/* Only the first old_size bytes must be preserved */
if (!check_pattern(blk->ptr, old_size, pattern))
{
fprintf(stderr, "[ERROR] pattern mismatch after realloc grow (idx=%d)\n", idx);
abort();
}
/* Now enforce our invariant for the future */
fill_pattern(blk->ptr, blk->size, blk->pattern);
}
}
else if (op == 1)
{
/* free */
if (blk->ptr)
{
free(blk->ptr);
blk->ptr = NULL;
blk->size = 0;
blk->is_calloc = 0;
}
}
else if (op == 2)
{
/* explicit realloc up (if exists) */
if (blk->ptr)
{
size_t old_size = blk->size;
size_t new_sz = old_size + 1 + (rand() % (old_size + 1));
unsigned char pattern = blk->pattern;
void *new_ptr = realloc(blk->ptr, new_sz);
if (!new_ptr)
{
fprintf(stderr, "[ERROR] realloc (grow2) returned NULL\n");
continue;
}
blk->ptr = new_ptr;
blk->size = new_sz;
blk->pattern = pattern;
if (!check_pattern(blk->ptr, old_size, pattern))
{
fprintf(stderr, "[ERROR] pattern mismatch after realloc grow2 (idx=%d)\n", idx);
abort();
}
fill_pattern(blk->ptr, blk->size, blk->pattern);
}
}
else /* op == 3, realloc down */
{
if (blk->ptr && blk->size > 1)
{
size_t old_size = blk->size;
size_t new_sz = 1 + (rand() % blk->size);
unsigned char pattern = blk->pattern;
void *new_ptr = realloc(blk->ptr, new_sz);
if (!new_ptr)
{
fprintf(stderr, "[ERROR] realloc (shrink) returned NULL\n");
continue;
}
blk->ptr = new_ptr;
blk->size = new_sz;
blk->pattern = pattern;
/* For shrink, we only expect the first new_sz bytes to still match */
if (!check_pattern(blk->ptr, blk->size, blk->pattern))
{
fprintf(stderr, "[ERROR] pattern mismatch after realloc shrink (idx=%d)\n", idx);
abort();
}
/* Still keep invariant explicitly (optional here) */
fill_pattern(blk->ptr, blk->size, blk->pattern);
}
}
if ((iter % 50000) == 0 && iter != 0)
printf(" random_stress progress: %d / %d\n", iter, RANDOM_ITERS);
}
free_all_blocks();
printf("random_stress: DONE\n\n");
}
/* Fragmentation test: allocate many small then free some, then big allocs, etc. */
static void fragmentation_test(void)
{
printf("== fragmentation_test ==\n");
const int count = 10000;
void **arr = malloc(sizeof(void *) * count);
size_t *sizes = malloc(sizeof(size_t) * count);
if (!arr || !sizes)
{
fprintf(stderr, "[ERROR] fragmentation_test: malloc failed\n");
free(arr);
free(sizes);
return;
}
/* Step 1: allocate random small blocks */
for (int i = 0; i < count; ++i)
{
size_t sz = 1 + (rand() % SMALL_MAX);
arr[i] = malloc(sz);
sizes[i] = sz;
if (!arr[i])
{
fprintf(stderr, "[ERROR] fragmentation_test: malloc failed at i=%d\n", i);
continue;
}
memset(arr[i], 0x5A, sz);
}
/* Step 2: free every other block */
for (int i = 0; i < count; i += 2)
{
if (arr[i])
{
free(arr[i]);
arr[i] = NULL;
sizes[i] = 0;
}
}
/* Step 3: allocate a bunch of bigger blocks, try to reuse fragmentation */
const int big_count = 1000;
void **big = malloc(sizeof(void *) * big_count);
size_t *big_sz = malloc(sizeof(size_t) * big_count);
if (!big || !big_sz)
{
fprintf(stderr, "[ERROR] fragmentation_test: big malloc failed\n");
free(big);
free(big_sz);
free(arr);
free(sizes);
return;
}
for (int i = 0; i < big_count; ++i)
{
size_t sz = SMALL_MAX + (rand() % (SMALL_MAX * 4));
big[i] = malloc(sz);
big_sz[i] = sz;
if (!big[i])
{
fprintf(stderr, "[ERROR] fragmentation_test: big malloc failed at i=%d\n", i);
continue;
}
memset(big[i], 0x6B, sz);
}
/* cleanup */
for (int i = 0; i < count; ++i)
free(arr[i]);
for (int i = 0; i < big_count; ++i)
free(big[i]);
free(arr);
free(sizes);
free(big);
free(big_sz);
printf("fragmentation_test: DONE\n\n");
}
int main(void)
{
srand((unsigned int)time(NULL));
basic_tests();
random_stress();
fragmentation_test();
printf("ALL TESTS DONE\n");
return 0;
}