DEV Community

Hedy
Hedy

Posted on

How to check for memory leak in microcontroller?

Memory leaks on microcontrollers are usually heap leaks (malloc/new without free/delete) or resource leaks (OS objects, buffers, drivers). Many embedded projects avoid the heap, so the “best” method depends on whether you use bare-metal or an RTOS.

1) Fast ways to detect leaks (works on most MCUs)
A) Track heap usage over time (high signal, low effort)

If you use malloc/free (or new/delete), periodically sample:

  • current free heap
  • minimum ever free heap (watermark)
  • largest free block (fragmentation indicator)

Red flag: free heap steadily decreases after repeated operations (e.g., every reconnect, every command, every loop).

How:

  • FreeRTOS: xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize()
  • newlib (common GCC embedded): mallinfo() / mallinfo2() (availability varies)
  • Vendor libs sometimes provide heap stats.

B) Add a “heap watermark” canary (bare-metal friendly)

If you own the heap region (or a big static arena), fill it with a pattern at boot (e.g., 0xA5) and later scan to see how much got overwritten.

Useful when: you don’t have good heap introspection, or you use a custom allocator.

C) Instrument allocations (most reliable)

Wrap malloc/free (and new/delete) to log:

  • pointer
  • size
  • callsite (file/line) or return address
  • running totals

Red flag: allocations not matched by frees after a test scenario.

Minimal embedded approach:

  • keep a small ring buffer of recent alloc/free events
  • keep counters: total alloc bytes, total freed bytes, outstanding allocations

D) Look for stack growth vs heap collision (classic embedded failure)

On MCUs where heap grows up and stack grows down, a “leak” can look like random crashes.
Monitor:

  • stack high-water mark per task (RTOS) or main stack usage (bare-metal)
  • heap end pointer

2) If you’re on an RTOS (FreeRTOS example)

Leak-like symptoms often come from:

  • tasks created repeatedly and not deleted
  • queues/semaphores/timers created repeatedly
  • network stack buffers not returned

Check:

  • uxTaskGetStackHighWaterMark() per task (stack overflow can mimic leaks)
  • xPortGetMinimumEverFreeHeapSize() (heap watermark)
  • Ensure every create has a corresponding delete where appropriate:

vTaskDelete(), vQueueDelete(), vSemaphoreDelete(), etc.

Best practice: create OS objects once at startup; don’t create/destroy repeatedly in runtime loops.

3) If you’re bare-metal (no RTOS)
A) Avoid heap entirely (most robust)

Use:

  • static buffers
  • memory pools
  • fixed-size ring buffers

B) If you must use heap: add hard guards

  • forbid dynamic allocation after init (“heap lock”)
  • assert if malloc/new is called after a certain point
  • use a pool allocator with fixed blocks

4) Practical test plan (what you actually run)

Pick a scenario that repeats:

  • connect/disconnect Wi-Fi (or BLE)
  • open/close a socket
  • parse a command message
  • start/stop a sensor sampling mode

Then run:

  1. log heap free + min-ever-free every N cycles
  2. run 1,000–100,000 cycles
  3. compare start vs end

If end free heap is lower and never returns, you likely have a leak (or fragmentation).

5) Fragmentation vs leak (important distinction)

A microcontroller can “run out of memory” even if you free everything, because of fragmentation.

Signs of fragmentation:

  • free heap looks “okay”, but allocations fail
  • “largest free block” keeps shrinking
  • many different allocation sizes over time

Fixes:

  • allocate same-sized blocks (pool)
  • avoid frequent allocate/free of variable sizes
  • reuse buffers

6) Tooling options (when you can use them)

  • Host-side tests: compile parts of code for PC and run ASan/LSan/Valgrind (great for finding logic leaks in parsers/protocol code)
  • RTT/UART logging: stream allocation logs off-device
  • Linker map review: ensure you’re not confusing BSS growth with heap issues

On-target Valgrind/ASan is typically not feasible for small MCUs, but host simulation often catches the same bug patterns.

7) Common embedded leak sources (checklist)

  • String usage (Arduino-style) causing heap churn/fragmentation
  • JSON parsing libs allocating internally
  • networking stacks (TCP reconnect path)
  • repeated “init” functions that allocate but don’t deinit
  • new in constructors without matching delete (or objects stored but never released)
  • error paths that return early without freeing

Top comments (0)