Memory Leaks & Debugging

Identifying and fixing memory-related issues

🐛 Memory Leaks and Debugging

Memory leaks occur when allocated memory isn't freed, causing programs to consume increasing memory. Debugging tools help identify and fix these issues.


// Memory leak example
int *ptr = malloc(100 * sizeof(int));
// Missing free(ptr); - causes memory leak!
return 0;  // Memory never released
                                    

Common Memory Issues

💧

Memory Leaks

Allocated memory not freed

malloc() without free()
💥

Double Free

Calling free() twice

free(ptr); free(ptr);
👻

Use After Free

Accessing freed memory

free(ptr); *ptr = 10;
📊

Buffer Overflow

Writing beyond allocated space

ptr[size] = value;

🔹 Memory Leak Examples

Memory leaks occur when allocated memory isn't freed before program termination. Common causes include forgotten free() calls, lost pointers through reassignment, and memory allocated in error paths. Leaks accumulate, consuming system resources and degrading performance over time. Detecting leaks early prevents serious issues. Use memory debugging tools to identify and fix leaks systematically. Understanding leak patterns helps you write cleaner code.

#include <stdio.h>
#include <stdlib.h>

// BAD: Function with memory leak
void memory_leak_example() {
    int *data = malloc(1000 * sizeof(int));
    
    // Do some work with data...
    for (int i = 0; i < 1000; i++) {
        data[i] = i;
    }
    
    // PROBLEM: Missing free(data)!
    // Memory is never released
}

// GOOD: Proper memory management
void proper_memory_management() {
    int *data = malloc(1000 * sizeof(int));
    
    if (data == NULL) {
        printf("Memory allocation failed!\n");
        return;
    }
    
    // Do some work with data...
    for (int i = 0; i < 1000; i++) {
        data[i] = i;
    }
    
    // SOLUTION: Always free allocated memory
    free(data);
    data = NULL;  // Prevent accidental reuse
}

int main() {
    // This will leak memory each time it's called
    for (int i = 0; i < 10; i++) {
        memory_leak_example();  // Leaks 4KB each call
    }
    
    // This properly manages memory
    proper_memory_management();
    
    return 0;
}

Result:

First function leaks 40KB total (10 calls × 4KB)
Second function properly frees all memory

🔹 Debugging Memory Issues

Simple techniques effectively identify and resolve memory problems in C programs. Use valgrind or Address Sanitizer to detect leaks, invalid accesses, and allocation errors automatically. Add debugging print statements to track memory allocation and deallocation. Use gdb debugger to inspect pointer values and memory contents. These tools provide invaluable insights into program behavior, making debugging faster and more systematic.

#include <stdio.h>
#include <stdlib.h>

// Debug wrapper for malloc
void* debug_malloc(size_t size, const char* file, int line) {
    void* ptr = malloc(size);
    printf("MALLOC: %p (%zu bytes) at %s:%d\n", ptr, size, file, line);
    return ptr;
}

// Debug wrapper for free
void debug_free(void* ptr, const char* file, int line) {
    printf("FREE: %p at %s:%d\n", ptr, file, line);
    free(ptr);
}

// Macros for easy debugging
#define DEBUG_MALLOC(size) debug_malloc(size, __FILE__, __LINE__)
#define DEBUG_FREE(ptr) debug_free(ptr, __FILE__, __LINE__)

int main() {
    printf("=== Memory Debug Example ===\n");
    
    // Allocate memory with debug info
    int *numbers = DEBUG_MALLOC(5 * sizeof(int));
    
    if (numbers != NULL) {
        // Use the memory
        for (int i = 0; i < 5; i++) {
            numbers[i] = i * 10;
        }
        
        // Free memory with debug info
        DEBUG_FREE(numbers);
    }
    
    printf("=== Debug Complete ===\n");
    return 0;
}

Output:

=== Memory Debug Example ===
MALLOC: 0x1234567 (20 bytes) at program.c:25
FREE: 0x1234567 at program.c:33
=== Debug Complete ===

🔹 Memory Leak Detection

Implement simple memory tracking systems to monitor allocation and deallocation automatically. Create wrapper functions around malloc() and free() that log all operations. Track allocated blocks with metadata like size and allocation site. Compare allocation and deallocation counts to detect unreleased memory. This proactive approach catches memory issues during development, preventing production problems. Building tracking systems strengthens debugging skills and improves code quality significantly.

#include <stdio.h>
#include <stdlib.h>

// Global counters for tracking
static int malloc_count = 0;
static int free_count = 0;
static size_t total_allocated = 0;

// Tracked malloc
void* tracked_malloc(size_t size) {
    void* ptr = malloc(size);
    if (ptr != NULL) {
        malloc_count++;
        total_allocated += size;
        printf("Allocated %zu bytes (Total: %d allocs, %zu bytes)\n", 
               size, malloc_count, total_allocated);
    }
    return ptr;
}

// Tracked free
void tracked_free(void* ptr) {
    if (ptr != NULL) {
        free(ptr);
        free_count++;
        printf("Freed memory (Total frees: %d)\n", free_count);
    }
}

// Check for leaks
void check_memory_leaks() {
    printf("\n=== Memory Leak Report ===\n");
    printf("Total allocations: %d\n", malloc_count);
    printf("Total frees: %d\n", free_count);
    
    if (malloc_count == free_count) {
        printf("✅ No memory leaks detected!\n");
    } else {
        printf("❌ Memory leak detected! %d allocations not freed.\n", 
               malloc_count - free_count);
    }
}

int main() {
    // Allocate some memory
    int *arr1 = tracked_malloc(10 * sizeof(int));
    int *arr2 = tracked_malloc(20 * sizeof(int));
    
    // Free only one (simulate leak)
    tracked_free(arr1);
    // arr2 is not freed - this creates a leak
    
    // Check for leaks
    check_memory_leaks();
    
    return 0;
}

Output:

Allocated 40 bytes (Total: 1 allocs, 40 bytes)
Allocated 80 bytes (Total: 2 allocs, 120 bytes)
Freed memory (Total frees: 1)

=== Memory Leak Report ===
Total allocations: 2
Total frees: 1
❌ Memory leak detected! 1 allocations not freed.

🔹 Prevention Strategies

Prevent memory issues through disciplined coding practices and defensive programming techniques. Use consistent allocation and deallocation patterns throughout your codebase. Implement clear ownership semantics for allocated memory. Avoid returning pointers to local variables. Use static analysis tools to catch issues automatically. Develop modular code with clear responsibility boundaries. These strategies create reliable, maintainable programs less susceptible to memory-related bugs and failures.

🛡️ Prevention Techniques

  • RAII Pattern: Allocate and free in same function scope
  • NULL Checks: Always check malloc return values
  • Pointer Nullification: Set pointers to NULL after free
  • Consistent Pairing: Every malloc should have a corresponding free
  • Error Handling: Clean up on error conditions

🔧 Debugging Tools

  • Valgrind: Memory error detector (Linux/Mac)
  • AddressSanitizer: Built into GCC/Clang
  • Static Analysis: Tools like Clang Static Analyzer
  • Custom Wrappers: Debug malloc/free functions

🧠 Test Your Knowledge

What happens when you call free() twice on the same pointer?