The "Holy Bible" for embedded engineers
Memory leaks occur when allocated memory is not properly deallocated, leading to gradual memory exhaustion. In embedded systems, memory leaks can be catastrophic due to limited resources and long uptimes.
// Classic memory leak - allocated but never freed
void classic_memory_leak() {
void* ptr = malloc(1024);
// Use ptr...
// Forgot to call free(ptr) - LEAK!
}
// Memory leak in error path
void leak_in_error_path() {
void* ptr1 = malloc(512);
if (ptr1 == NULL) return; // Early return without cleanup
void* ptr2 = malloc(256);
if (ptr2 == NULL) {
// Forgot to free ptr1 - LEAK!
return;
}
// Use both pointers...
free(ptr1);
free(ptr2);
}
// File handle leak
void file_handle_leak() {
FILE* file = fopen("data.txt", "r");
if (file == NULL) return;
// Process file...
// Forgot to call fclose(file) - RESOURCE LEAK!
}
// Mutex leak
#include <pthread.h>
void mutex_leak() {
pthread_mutex_t* mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex, NULL);
// Use mutex...
// Forgot to call pthread_mutex_destroy() and free() - LEAK!
}
typedef struct node {
int data;
struct node* next;
} node_t;
void circular_reference_leak() {
node_t* head = malloc(sizeof(node_t));
node_t* tail = malloc(sizeof(node_t));
head->next = tail;
tail->next = head; // Circular reference
// If we only free head, tail becomes unreachable
free(head);
// tail is now leaked!
}
// Enable all warnings for memory leak detection
// gcc -Wall -Wextra -Werror -fsanitize=address
void potential_leak_function() {
void* ptr = malloc(100);
// Compiler warning: variable 'ptr' set but not used
// This could indicate a leak
}
// clang --analyze -Xanalyzer -analyzer-output=text
void analyzed_function() {
void* ptr = malloc(100);
if (some_condition()) {
free(ptr);
return;
}
// Static analyzer detects: ptr is not freed in this path
}
// Simple static analysis for common patterns
#include <regex.h>
void check_for_malloc_without_free(const char* source_code) {
regex_t malloc_regex, free_regex;
regcomp(&malloc_regex, "malloc\\s*\\(", REG_EXTENDED);
regcomp(&free_regex, "free\\s*\\(", REG_EXTENDED);
// Count malloc and free calls
// If malloc_count > free_count, potential leak
}
// Compile with: gcc -fsanitize=address -g
void asan_demo() {
void* ptr = malloc(100);
// Use ptr...
// If we forget to free(ptr), ASan will report leak
// ASan also detects:
// - Buffer overflows
// - Use-after-free
// - Double-free
}
// ASan output example:
// ==12345==ERROR: LeakSanitizer: detected memory leaks
// ==12345==SUMMARY: AddressSanitizer: 1 byte(s) leaked in 1 allocation(s)
// Run with: valgrind --leak-check=full --show-leak-kinds=all
void valgrind_demo() {
void* ptr = malloc(100);
// Use ptr...
// Valgrind will report:
// ==12345== 100 bytes in 1 blocks are definitely lost
// ==12345== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck.so)
// ==12345== by 0x400544: valgrind_demo (demo.c:5)
}
typedef struct {
void* ptr;
size_t size;
const char* file;
int line;
bool freed;
} allocation_record_t;
typedef struct {
allocation_record_t* records;
size_t count;
size_t capacity;
} memory_tracker_t;
memory_tracker_t* create_memory_tracker(size_t initial_capacity) {
memory_tracker_t* tracker = malloc(sizeof(memory_tracker_t));
if (!tracker) return NULL;
tracker->records = calloc(initial_capacity, sizeof(allocation_record_t));
tracker->count = 0;
tracker->capacity = initial_capacity;
return tracker;
}
void* tracked_malloc(size_t size, const char* file, int line) {
void* ptr = malloc(size);
if (ptr) {
// Add to tracker
allocation_record_t record = {
.ptr = ptr,
.size = size,
.file = file,
.line = line,
.freed = false
};
add_allocation_record(&record);
}
return ptr;
}
void tracked_free(void* ptr) {
if (ptr) {
// Mark as freed in tracker
mark_as_freed(ptr);
free(ptr);
}
}
typedef struct {
void* pool;
size_t block_size;
size_t total_blocks;
bool* allocated_blocks;
size_t allocated_count;
} leak_detecting_pool_t;
leak_detecting_pool_t* create_leak_detecting_pool(size_t block_size,
size_t num_blocks) {
leak_detecting_pool_t* pool = malloc(sizeof(leak_detecting_pool_t));
if (!pool) return NULL;
pool->block_size = block_size;
pool->total_blocks = num_blocks;
pool->allocated_count = 0;
pool->pool = malloc(block_size * num_blocks);
pool->allocated_blocks = calloc(num_blocks, sizeof(bool));
return pool;
}
void* pool_allocate_with_tracking(leak_detecting_pool_t* pool) {
for (size_t i = 0; i < pool->total_blocks; i++) {
if (!pool->allocated_blocks[i]) {
pool->allocated_blocks[i] = true;
pool->allocated_count++;
return (char*)pool->pool + (i * pool->block_size);
}
}
return NULL;
}
void pool_free_with_tracking(leak_detecting_pool_t* pool, void* ptr) {
size_t block_index = ((char*)ptr - (char*)pool->pool) / pool->block_size;
if (block_index < pool->total_blocks && pool->allocated_blocks[block_index]) {
pool->allocated_blocks[block_index] = false;
pool->allocated_count--;
}
}
void report_pool_leaks(leak_detecting_pool_t* pool) {
if (pool->allocated_count > 0) {
printf("MEMORY LEAK: %zu blocks not freed in pool\n",
pool->allocated_count);
for (size_t i = 0; i < pool->total_blocks; i++) {
if (pool->allocated_blocks[i]) {
printf(" Block %zu still allocated\n", i);
}
}
}
}
// Track allocations on stack for embedded systems
#define MAX_STACK_ALLOCATIONS 100
typedef struct {
void* ptr;
const char* function;
int line;
} stack_allocation_t;
static stack_allocation_t allocation_stack[MAX_STACK_ALLOCATIONS];
static int stack_top = 0;
void* stack_tracked_malloc(size_t size, const char* function, int line) {
void* ptr = malloc(size);
if (ptr && stack_top < MAX_STACK_ALLOCATIONS) {
allocation_stack[stack_top].ptr = ptr;
allocation_stack[stack_top].function = function;
allocation_stack[stack_top].line = line;
stack_top++;
}
return ptr;
}
void stack_tracked_free(void* ptr) {
if (ptr) {
// Find and remove from stack
for (int i = 0; i < stack_top; i++) {
if (allocation_stack[i].ptr == ptr) {
// Remove by shifting
for (int j = i; j < stack_top - 1; j++) {
allocation_stack[j] = allocation_stack[j + 1];
}
stack_top--;
break;
}
}
free(ptr);
}
}
void check_stack_leaks() {
if (stack_top > 0) {
printf("STACK LEAKS DETECTED: %d allocations not freed\n", stack_top);
for (int i = 0; i < stack_top; i++) {
printf(" %s:%d - %p\n",
allocation_stack[i].function,
allocation_stack[i].line,
allocation_stack[i].ptr);
}
}
}
typedef struct {
uint32_t total_allocations;
uint32_t total_frees;
uint32_t current_allocated;
uint32_t peak_allocated;
} rt_leak_monitor_t;
static rt_leak_monitor_t leak_monitor = {0};
void* rt_tracked_malloc(size_t size) {
void* ptr = malloc(size);
if (ptr) {
leak_monitor.total_allocations++;
leak_monitor.current_allocated++;
if (leak_monitor.current_allocated > leak_monitor.peak_allocated) {
leak_monitor.peak_allocated = leak_monitor.current_allocated;
}
}
return ptr;
}
void rt_tracked_free(void* ptr) {
if (ptr) {
leak_monitor.total_frees++;
leak_monitor.current_allocated--;
free(ptr);
}
}
void report_rt_leaks() {
printf("Allocations: %u, Frees: %u, Current: %u, Peak: %u\n",
leak_monitor.total_allocations,
leak_monitor.total_frees,
leak_monitor.current_allocated,
leak_monitor.peak_allocated);
if (leak_monitor.current_allocated > 0) {
printf("POTENTIAL LEAK: %u blocks not freed\n",
leak_monitor.current_allocated);
}
}
// Check for leaks periodically in real-time systems
void periodic_leak_check() {
static uint32_t check_counter = 0;
check_counter++;
if (check_counter % 10000 == 0) { // Check every 10k allocations
uint32_t leak_count = leak_monitor.total_allocations - leak_monitor.total_frees;
if (leak_count > 100) { // Threshold for leak detection
printf("WARNING: Potential memory leak detected\n");
report_rt_leaks();
}
}
}
// WRONG: Leak in error path
void* allocate_with_error_leak() {
void* ptr1 = malloc(100);
if (!ptr1) return NULL;
void* ptr2 = malloc(200);
if (!ptr2) {
// LEAK: ptr1 not freed
return NULL;
}
// Use both pointers...
free(ptr1);
free(ptr2);
return ptr2;
}
// CORRECT: Proper cleanup
void* allocate_with_cleanup() {
void* ptr1 = malloc(100);
if (!ptr1) return NULL;
void* ptr2 = malloc(200);
if (!ptr2) {
free(ptr1); // Clean up before returning
return NULL;
}
// Use both pointers...
free(ptr1);
free(ptr2);
return ptr2;
}
// WRONG: Leak in loop
void loop_leak() {
for (int i = 0; i < 1000; i++) {
void* ptr = malloc(100);
// Use ptr...
// Forgot to free(ptr) - LEAK!
}
}
// CORRECT: Free in loop
void loop_no_leak() {
for (int i = 0; i < 1000; i++) {
void* ptr = malloc(100);
// Use ptr...
free(ptr); // Free before next iteration
}
}
// WRONG: Multiple exit points without cleanup
void* function_with_exit_leaks() {
void* ptr = malloc(100);
if (condition1()) {
return ptr; // LEAK: ptr not freed
}
if (condition2()) {
return NULL; // LEAK: ptr not freed
}
// Use ptr...
free(ptr);
return NULL;
}
// CORRECT: Single exit point or proper cleanup
void* function_with_cleanup() {
void* ptr = malloc(100);
if (!ptr) return NULL;
void* result = NULL;
if (condition1()) {
result = ptr;
} else if (condition2()) {
free(ptr);
result = NULL;
} else {
// Use ptr...
free(ptr);
result = NULL;
}
return result;
}
typedef struct {
void* ptr;
void (*cleanup)(void*);
} raii_wrapper_t;
raii_wrapper_t* create_raii_wrapper(void* ptr, void (*cleanup)(void*)) {
raii_wrapper_t* wrapper = malloc(sizeof(raii_wrapper_t));
if (wrapper) {
wrapper->ptr = ptr;
wrapper->cleanup = cleanup;
}
return wrapper;
}
void destroy_raii_wrapper(raii_wrapper_t* wrapper) {
if (wrapper && wrapper->cleanup) {
wrapper->cleanup(wrapper->ptr);
}
free(wrapper);
}
// Usage
void example_raii() {
raii_wrapper_t* wrapper = create_raii_wrapper(
malloc(100),
free
);
// Use wrapper->ptr...
destroy_raii_wrapper(wrapper); // Automatically frees ptr
}
typedef struct {
void* pool;
size_t block_size;
size_t total_blocks;
bool* allocated_blocks;
} auto_cleanup_pool_t;
auto_cleanup_pool_t* create_auto_cleanup_pool(size_t block_size,
size_t num_blocks) {
auto_cleanup_pool_t* pool = malloc(sizeof(auto_cleanup_pool_t));
if (!pool) return NULL;
pool->block_size = block_size;
pool->total_blocks = num_blocks;
pool->pool = malloc(block_size * num_blocks);
pool->allocated_blocks = calloc(num_blocks, sizeof(bool));
return pool;
}
void destroy_auto_cleanup_pool(auto_cleanup_pool_t* pool) {
if (pool) {
// Check for leaks before destroying
size_t leak_count = 0;
for (size_t i = 0; i < pool->total_blocks; i++) {
if (pool->allocated_blocks[i]) {
leak_count++;
}
}
if (leak_count > 0) {
printf("WARNING: %zu blocks leaked in pool\n", leak_count);
}
free(pool->pool);
free(pool->allocated_blocks);
free(pool);
}
}
typedef struct {
void* ptr;
int ref_count;
} smart_ptr_t;
smart_ptr_t* create_smart_ptr(void* ptr) {
smart_ptr_t* smart = malloc(sizeof(smart_ptr_t));
if (smart) {
smart->ptr = ptr;
smart->ref_count = 1;
}
return smart;
}
smart_ptr_t* smart_ptr_ref(smart_ptr_t* smart) {
if (smart) {
smart->ref_count++;
}
return smart;
}
void smart_ptr_unref(smart_ptr_t* smart) {
if (smart) {
smart->ref_count--;
if (smart->ref_count <= 0) {
free(smart->ptr);
free(smart);
}
}
}
// Define allocation patterns
#define ALLOCATE_AND_CHECK(ptr, size) \
do { \
ptr = malloc(size); \
if (!ptr) { \
fprintf(stderr, "Allocation failed at %s:%d\n", __FILE__, __LINE__); \
return NULL; \
} \
} while(0)
#define FREE_AND_NULL(ptr) \
do { \
if (ptr) { \
free(ptr); \
ptr = NULL; \
} \
} while(0)
// Usage
void* safe_allocation_example() {
void* ptr1 = NULL, *ptr2 = NULL;
ALLOCATE_AND_CHECK(ptr1, 100);
ALLOCATE_AND_CHECK(ptr2, 200);
// Use pointers...
FREE_AND_NULL(ptr2);
FREE_AND_NULL(ptr1);
return NULL;
}
#ifdef DEBUG
#define MALLOC(size) debug_malloc(size, __FILE__, __LINE__)
#define FREE(ptr) debug_free(ptr, __FILE__, __LINE__)
#else
#define MALLOC(size) malloc(size)
#define FREE(ptr) free(ptr)
#endif
void* debug_malloc(size_t size, const char* file, int line) {
void* ptr = malloc(size);
if (ptr) {
printf("DEBUG: malloc(%zu) at %s:%d -> %p\n", size, file, line, ptr);
add_to_debug_tracker(ptr, size, file, line);
}
return ptr;
}
void debug_free(void* ptr, const char* file, int line) {
if (ptr) {
printf("DEBUG: free(%p) at %s:%d\n", ptr, file, line);
remove_from_debug_tracker(ptr);
free(ptr);
}
}
void perform_memory_audit() {
struct mallinfo info = mallinfo();
printf("Memory Audit:\n");
printf(" Total allocated: %d bytes\n", info.uordblks);
printf(" Total free: %d bytes\n", info.fordblks);
printf(" Largest free block: %d bytes\n", info.mxordblk);
printf(" Number of free blocks: %d\n", info.ordblks);
// Check for potential leaks
if (info.uordblks > 0 && info.fordblks < 1000) {
printf("WARNING: Low free memory - potential leak?\n");
}
}
Next Topic: Stack Overflow Prevention → Memory Protection