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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
