8#if defined(DEBUG_MEMORY) && !defined(NDEBUG)
23typedef struct mem_block {
29 struct mem_block *next;
32static __thread
bool g_in_debug_memory =
false;
36 atomic_size_t total_allocated;
37 atomic_size_t total_freed;
38 atomic_size_t current_usage;
39 atomic_size_t peak_usage;
40 atomic_size_t malloc_calls;
41 atomic_size_t free_calls;
42 atomic_size_t calloc_calls;
43 atomic_size_t realloc_calls;
45 atomic_int mutex_state;
47} g_mem = {.head = NULL,
64static atomic_flag g_logged_mutex_init_failure = ATOMIC_FLAG_INIT;
66static bool ensure_mutex_initialized(
void) {
68 int state = atomic_load_explicit(&g_mem.mutex_state, memory_order_acquire);
75 if (atomic_compare_exchange_strong_explicit(&g_mem.mutex_state, &expected, 1, memory_order_acq_rel,
76 memory_order_acquire)) {
78 atomic_store_explicit(&g_mem.mutex_state, 2, memory_order_release);
82 atomic_store_explicit(&g_mem.mutex_state, 0, memory_order_release);
83 if (!atomic_flag_test_and_set(&g_logged_mutex_init_failure)) {
84 log_error(
"Failed to initialize debug memory mutex; memory tracking will run without locking");
95void *debug_malloc(
size_t size,
const char *file,
int line) {
96 void *ptr = (
void *)malloc(size);
100 if (g_in_debug_memory) {
104 g_in_debug_memory =
true;
106 atomic_fetch_add(&g_mem.malloc_calls, 1);
107 atomic_fetch_add(&g_mem.total_allocated, size);
108 size_t new_usage = atomic_fetch_add(&g_mem.current_usage, size) + size;
110 size_t peak = atomic_load(&g_mem.peak_usage);
111 while (new_usage > peak) {
112 if (atomic_compare_exchange_weak(&g_mem.peak_usage, &peak, new_usage))
116 bool have_mutex = ensure_mutex_initialized();
120 mem_block_t *block = (mem_block_t *)malloc(
sizeof(mem_block_t));
124 block->is_aligned =
false;
126 SAFE_STRNCPY(block->file, normalized_file,
sizeof(block->file) - 1);
128 block->next = g_mem.head;
135 g_in_debug_memory =
false;
139void debug_track_aligned(
void *ptr,
size_t size,
const char *file,
int line) {
143 if (g_in_debug_memory) {
147 g_in_debug_memory =
true;
149 atomic_fetch_add(&g_mem.malloc_calls, 1);
150 atomic_fetch_add(&g_mem.total_allocated, size);
151 size_t new_usage = atomic_fetch_add(&g_mem.current_usage, size) + size;
153 size_t peak = atomic_load(&g_mem.peak_usage);
154 while (new_usage > peak) {
155 if (atomic_compare_exchange_weak(&g_mem.peak_usage, &peak, new_usage))
159 bool have_mutex = ensure_mutex_initialized();
163 mem_block_t *block = (mem_block_t *)malloc(
sizeof(mem_block_t));
167 block->is_aligned =
true;
169 SAFE_STRNCPY(block->file, normalized_file,
sizeof(block->file) - 1);
171 block->next = g_mem.head;
178 g_in_debug_memory =
false;
181void debug_free(
void *ptr,
const char *file,
int line) {
185 if (g_in_debug_memory) {
190 g_in_debug_memory =
true;
192 atomic_fetch_add(&g_mem.free_calls, 1);
194 size_t freed_size = 0;
197 bool was_aligned =
false;
200 bool have_mutex = ensure_mutex_initialized();
204 mem_block_t *prev = NULL;
205 mem_block_t *curr = g_mem.head;
208 if (curr->ptr == ptr) {
210 prev->next = curr->next;
212 g_mem.head = curr->next;
215 freed_size = curr->size;
218 was_aligned = curr->is_aligned;
238 atomic_fetch_add(&g_mem.total_freed, freed_size);
239 atomic_fetch_sub(&g_mem.current_usage, freed_size);
244 atomic_fetch_add(&g_mem.total_freed, real_size);
245 atomic_fetch_sub(&g_mem.current_usage, real_size);
259 g_in_debug_memory =
false;
262void *debug_calloc(
size_t count,
size_t size,
const char *file,
int line) {
263 size_t total = count * size;
264 void *ptr = calloc(count, size);
268 if (g_in_debug_memory) {
272 g_in_debug_memory =
true;
274 atomic_fetch_add(&g_mem.calloc_calls, 1);
275 atomic_fetch_add(&g_mem.total_allocated, total);
276 size_t new_usage = atomic_fetch_add(&g_mem.current_usage, total) + total;
278 size_t peak = atomic_load(&g_mem.peak_usage);
279 while (new_usage > peak) {
280 if (atomic_compare_exchange_weak(&g_mem.peak_usage, &peak, new_usage))
284 bool have_mutex = ensure_mutex_initialized();
288 mem_block_t *block = (mem_block_t *)malloc(
sizeof(mem_block_t));
292 block->is_aligned =
false;
293 SAFE_STRNCPY(block->file, file,
sizeof(block->file) - 1);
295 block->next = g_mem.head;
302 g_in_debug_memory =
false;
350void *debug_realloc(
void *ptr,
size_t size,
const char *file,
int line) {
352 if (g_in_debug_memory) {
353 return realloc(ptr, size);
356 g_in_debug_memory =
true;
359 atomic_fetch_add(&g_mem.realloc_calls, 1);
363 g_in_debug_memory =
false;
364 return debug_malloc(size, file, line);
368 g_in_debug_memory =
false;
369 debug_free(ptr, file, line);
375 bool have_mutex = ensure_mutex_initialized();
381 mem_block_t *curr = g_mem.head;
382 while (curr && curr->ptr != ptr) {
386 old_size = curr->size;
397 g_in_debug_memory =
false;
404 if (size >= old_size) {
406 size_t delta = size - old_size;
407 atomic_fetch_add(&g_mem.total_allocated, delta);
408 size_t new_usage = atomic_fetch_add(&g_mem.current_usage, delta) + delta;
411 size_t peak = atomic_load(&g_mem.peak_usage);
412 while (new_usage > peak) {
413 if (atomic_compare_exchange_weak(&g_mem.peak_usage, &peak, new_usage))
418 size_t delta = old_size - size;
419 atomic_fetch_add(&g_mem.total_freed, delta);
420 atomic_fetch_sub(&g_mem.current_usage, delta);
424 atomic_fetch_add(&g_mem.total_allocated, size);
425 size_t new_usage = atomic_fetch_add(&g_mem.current_usage, size) + size;
428 size_t peak = atomic_load(&g_mem.peak_usage);
429 while (new_usage > peak) {
430 if (atomic_compare_exchange_weak(&g_mem.peak_usage, &peak, new_usage))
436 if (ensure_mutex_initialized()) {
440 mem_block_t *curr = g_mem.head;
441 while (curr && curr->ptr != ptr) {
449 curr->is_aligned =
false;
451 curr->file[
sizeof(curr->file) - 1] =
'\0';
455 mem_block_t *block = (mem_block_t *)malloc(
sizeof(mem_block_t));
457 block->ptr = new_ptr;
459 block->is_aligned =
false;
460 SAFE_STRNCPY(block->file, file,
sizeof(block->file) - 1);
462 block->next = g_mem.head;
470 new_ptr, file, line);
473 g_in_debug_memory =
false;
477void debug_memory_set_quiet_mode(
bool quiet) {
478 g_mem.quiet_mode = quiet;
481static const char *strip_project_path(
const char *full_path) {
485void debug_memory_report(
void) {
488 bool quiet = g_mem.quiet_mode;
492 size_t total_allocated = atomic_load(&g_mem.total_allocated);
493 size_t total_freed = atomic_load(&g_mem.total_freed);
494 size_t current_usage = atomic_load(&g_mem.current_usage);
495 size_t peak_usage = atomic_load(&g_mem.peak_usage);
496 size_t malloc_calls = atomic_load(&g_mem.malloc_calls);
497 size_t calloc_calls = atomic_load(&g_mem.calloc_calls);
498 size_t free_calls = atomic_load(&g_mem.free_calls);
500 char pretty_total[64];
501 char pretty_freed[64];
502 char pretty_current[64];
503 char pretty_peak[64];
516 size_t diff = (malloc_calls + calloc_calls) - free_calls;
520 if (ensure_mutex_initialized()) {
524 mem_block_t *curr = g_mem.head;
526 char pretty_size[64];
529 safe_fprintf(stderr,
" - %s:%d - %s\n", strip_project_path(curr->file), curr->line, pretty_size));
536 safe_fprintf(stderr,
"\nCurrent allocations unavailable: failed to initialize debug memory mutex\n"));
542#elif defined(DEBUG_MEMORY)
544void debug_memory_set_quiet_mode(
bool quiet) {
548void debug_memory_report(
void) {}
550void *debug_malloc(
size_t size,
const char *file,
int line) {
556void *debug_calloc(
size_t count,
size_t size,
const char *file,
int line) {
559 return calloc(count, size);
562void *debug_realloc(
void *ptr,
size_t size,
const char *file,
int line) {
565 return realloc(ptr, size);
568void debug_free(
void *ptr,
const char *file,
int line) {
574void debug_track_aligned(
void *ptr,
size_t size,
const char *file,
int line) {
⚠️‼️ Error and/or exit() when things go bad.
🔍 Memory debugging helpers for tracking allocations in debug builds
#define SAFE_REALLOC(ptr, size, cast)
#define SAFE_STRNCPY(dst, src, size)
void asciichat_errno_cleanup(void)
Cleanup error system resources.
#define LOG_RATE_FAST
Log rate limit: 1 second (1,000,000 microseconds)
#define log_error(...)
Log an ERROR message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
void format_bytes_pretty(size_t bytes, char *out, size_t out_capacity)
Format byte count into human-readable string.
const char * extract_project_relative_path(const char *file)
Extract relative path from an absolute path.
Cross-platform mutex interface for ascii-chat.
📂 Path Manipulation Utilities
Cross-platform system functions interface for ascii-chat.