Fundamentals 8 min read

How to Prevent Common C Memory Leaks and Build an Automatic Debug Logger

This guide explains typical C memory‑allocation errors, practical ways to avoid them, and introduces a lightweight logging system that automatically records allocations and frees to help developers detect memory leaks during debugging.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How to Prevent Common C Memory Leaks and Build an Automatic Debug Logger

Common Mistakes and Prevention

Three frequent memory‑management bugs in C are demonstrated with minimal code examples and concrete prevention tips.

1. Forgetting to free allocated memory

void func(void)
{
    p = malloc(len);
    do_something(p);
    return;  /* error: memory not freed before exit */
}

Prevention: Ensure every malloc() has a matching free() before any return path.

int func(void)
{
    p = malloc(len);
    if (condition)
        return -1;  /* error: early return without freeing */
    free(p);
    return 0;
}

Prevention: Verify that all exit branches release the allocated block.

2. Freeing an invalid pointer

void func(void)
{
    p = malloc(len);
    val = *p++;  /* error: pointer moved, original address lost */
    free(p);
}

Prevention: Never modify the pointer returned by malloc(); copy it to another variable if you need to traverse the memory.

3. Allocating insufficient space leading to overflow

void func(void)
{
    len = strlen(str);
    p = malloc(len);
    strcpy(p, str);  /* error: missing space for terminating '\0' */
}

Prevention: Allocate strlen(str) + 1 bytes to accommodate the null terminator.

Automatic Error‑Detection Mechanism

To catch leaks that survive manual checks, a simple logging framework records each allocation’s address and size, then removes the entry on free. When the program terminates, any remaining log entries indicate leaked memory.

The core components are:

A DMEM_LOG structure that stores the pointer, size, and a link to the next log entry.

Static pools ( s_pstFreeLog, s_pstHeadLog) and a configurable pool size ( NUM_DMEM_LOG).

Functions to initialize the pool, insert a log, and remove a log.

/* Log of dynamic memory usage */
typedef struct _dmem_log {
    struct _dmem_log *p_stNext;   /* Point to next log */
    const void *p_vDMem;          /* Pointer to allocated memory */
    INT32S iSize;                 /* Size of the allocated block */
} DMEM_LOG;

static DMEM_LOG *s_pstFreeLog;   /* Free‑log pool head */
static INT8U s_byNumUsedLog;
static DMEM_LOG *s_pstHeadLog;   /* Used‑log chain head */
#define NUM_DMEM_LOG 20
static DMEM_LOG s_astDMemLog[NUM_DMEM_LOG];

Initialization walks the static array, linking each element into a free list.

static void InitDMemLog(void)
{
    for (INT16S nCnt = 0; nCnt < NUM_DMEM_LOG; ++nCnt) {
        s_astDMemLog[nCnt].p_stNext = &s_astDMemLog[nCnt + 1];
    }
    s_astDMemLog[NUM_DMEM_LOG - 1].p_stNext = NULL;
    s_pstFreeLog = &s_astDMemLog[0];
}

When an allocation occurs, LogDMem() extracts a node from the free pool, fills it with the address and size, and pushes it onto the used‑log chain.

static void LogDMem(const void *p_vAddr, INT32S iSize)
{
    ASSERT(p_vAddr && iSize > 0);
    DMEM_LOG *p_stLog;
    OS_ENTER_CRITICAL();
    if (!s_pstFreeLog) { OS_EXIT_CRITICAL(); PRINTF("Allocate DMemLog failed.
"); return; }
    p_stLog = s_pstFreeLog;
    s_pstFreeLog = s_pstFreeLog->p_stNext;
    OS_EXIT_CRITICAL();
    p_stLog->p_vDMem = p_vAddr;
    p_stLog->iSize = iSize;
    OS_ENTER_CRITICAL();
    p_stLog->p_stNext = s_pstHeadLog;
    s_pstHeadLog = p_stLog;
    ++s_byNumUsedLog;
    OS_EXIT_CRITICAL();
}

Conversely, UnlogDMem() searches the used chain for the matching address, removes the node, and returns it to the free pool.

static void UnlogDMem(const void *p_vAddr)
{
    ASSERT(p_vAddr);
    DMEM_LOG *p_stLog = s_pstHeadLog, *p_stPrev = NULL;
    OS_ENTER_CRITICAL();
    while (p_stLog && p_stLog->p_vDMem != p_vAddr) {
        p_stPrev = p_stLog;
        p_stLog = p_stLog->p_stNext;
    }
    if (!p_stLog) { OS_EXIT_CRITICAL(); PRINTF("Search Log failed.
"); return; }
    if (p_stLog == s_pstHeadLog) s_pstHeadLog = s_pstHeadLog->p_stNext;
    else p_stPrev->p_stNext = p_stLog->p_stNext;
    --s_byNumUsedLog;
    OS_EXIT_CRITICAL();
    p_stLog->p_vDMem = NULL;
    p_stLog->iSize = 0;
    OS_ENTER_CRITICAL();
    p_stLog->p_stNext = s_pstFreeLog;
    s_pstFreeLog = p_stLog;
    OS_EXIT_CRITICAL();
}

Wrapper functions replace the standard allocation APIs when debugging is enabled.

void *MallocExt(INT32S iSize)
{
    ASSERT(iSize > 0);
    void *p_vAddr = malloc(iSize);
    if (!p_vAddr) {
        PRINTF("malloc failed at %s line %d.
", __FILE__, __LINE__);
    } else {
#if DMEM_DBG && DBG_VER
        memset(p_vAddr, 0xA3, iSize); /* fill with pattern for debug */
        LogDMem(p_vAddr, iSize);
#endif
    }
    return p_vAddr;
}

void FreeExt(void *p_vMem)
{
    ASSERT(p_vMem);
    free(p_vMem);
#if DMEM_DBG && DBG_VER
    UnlogDMem(p_vMem);
#endif
}

When compiled in a non‑debug (release) build, MallocExt and FreeExt behave exactly like malloc and free, ensuring no performance penalty.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DebuggingMemory ManagementCloggingmallocFreedynamic memory
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.