What are Dangling Pointers in C- Best C Programming Course

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

PREV_LEC NEXT_LEC

Free C Programming Course · Pointers · Lecture 67

Dangling Pointers in C

A dangling pointer in C points at memory that no longer belongs to you. Learn how it happens, why embedded firmware is especially exposed, and four safe patterns to avoid it. Part of our free C tutorial for embedded systems.

4
Ways it happens
4
Safe patterns
2
Free tools to catch it

A dangling pointer in C is a pointer that still holds an address after the object living there has been destroyed. The pointer looks perfectly normal, the program compiles, and on a quiet day it even prints the right number. Then you add one more function call, or move to a different board, and the value turns to garbage. If you are following this free C programming course to prepare for embedded C programming, this is the bug you must learn to spot on sight.

In this lecture we revisit the classic textbook exercises where a function stores the address of a local variable in a global pointer, and where one function returns a pointer to a local while another returns a pointer to heap memory. We rewrite them with fresh examples, run them, and look at what the compiler and a sanitizer say.

What You Will Learn

  • What a dangling pointer is and how it differs from a wild pointer and a NULL pointer
  • The four common ways a dangling pointer is created
  • Why a local variable’s address is only valid until the function returns
  • Four safe ways to hand data back from a function
  • How to catch the bug with compiler warnings and AddressSanitizer
  • Why this matters on microcontrollers: ISRs, DMA and shared buffers

Prerequisites

You should be comfortable with pointer declaration, the address-of operator, dereferencing, and the idea of a function call. If any of that feels shaky, revisit the earlier pointer lectures in this free C tutorial first. You need a C compiler such as GCC or Clang on Linux, or any embedded toolchain that can build a host test program.

What Is a Dangling Pointer in C?

Every local variable lives in a stack frame that is created when the function is called and thrown away when it returns. The memory is not erased. The stack pointer simply moves back, and the next function call reuses the same bytes for its own locals. A pointer that remembers the address of a dead local is therefore pointing at a slot that now belongs to someone else.

Stack slot lifetime for a local variable
1. Before callmain frame onlystack slot free
2. Inside bind_sensor()main framereading = 42 (valid)g_sensor holds its address
3. After returnmain frameslot released, bytes still thereg_sensor still holds old address
4. Next callslot reused by another functiong_sensor now reads someone else’s data

Dangling vs Wild vs NULL

KindWhat it holdsDereference resultCure
NULL pointerAddress zero by conventionUsually a crash or hard fault, easy to detectCheck before use
Wild pointerNever initialised, random bitsUndefined behaviourAlways initialise at declaration
Dangling pointerAddress of an object that is goneUndefined behaviour, often looks fine at firstControl object lifetime, reset to NULL

Four Ways to Create a Dangling Pointer

  • Returning the address of a local variable. The classic mistake: return &sample; inside a function.
  • Storing a local’s address in a longer-lived pointer. A global, a struct field, or a registered callback context that outlives the function.
  • Use after free. Calling free(p) and then reading *p, or freeing the same block through a second pointer.
  • Leaving a block scope. A pointer to a variable declared inside an if or loop body used after the closing brace.

Fixing a Dangling Pointer in C: Four Safe Patterns

The program below shows three ways to return something useful from a function, plus the broken version for comparison. Save it as ep_dangling.c.

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

static int *read_bad(void)
{
    int sample = 512;
    return &sample;              /* address of a dead stack slot */
}

static int *read_static(void)
{
    static int last = 512;       /* lives for the whole program */
    return &last;
}

static int *read_heap(void)
{
    int *p = malloc(sizeof *p);
    if (p != NULL)
        *p = 768;
    return p;                    /* caller must free() */
}

static void read_into(int *out)
{
    *out = 1023;                 /* caller owns the storage */
}

int main(void)
{
    int *a = read_static();
    int *b = read_heap();
    int c = 0;

    read_into(&c);
    printf("static: %d  heap: %d  caller-owned: %d\n", *a, b ? *b : -1, c);
    free(b);
    (void)read_bad;
    return 0;
}

Build and Run

$ gcc -Wall -Wextra -std=c11 -o ep_dangling ep_dangling.c
ep_dangling.c: In function 'read_bad':
ep_dangling.c:7:12: warning: function returns address of local variable [-Wreturn-local-addr]
    7 |     return &sample;              /* address of a dead stack slot */
      |            ^~~~~~~
$ ./ep_dangling
static: 512  heap: 768  caller-owned: 1023

The compiler already caught read_bad() for us. Treat every warning with that text as a build failure. The other three functions are the safe patterns:

PatternWho owns the memoryEmbedded suitability
Caller-provided buffer (read_into)The callerBest choice. No heap, predictable, easy to review.
static local (read_static)The program, for its whole lifeGood, but not reentrant. Dangerous if an ISR and main both call it.
Heap block (read_heap)Caller must freeOften avoided in firmware. Many coding standards, MISRA C among them, restrict dynamic allocation.
Pointer into the caller’s own arrayThe callerExcellent. You will see it in the final lecture of this chapter.

The Global Pointer Variant

The compiler does not always warn when the dangling address is stored in a global. Here is a quieter version, ep_dangling_bad.c:

#include <stdio.h>

static int *g_sensor;                /* file-scope pointer outlives every call */

static void bind_sensor(void)
{
    int reading = 42;
    g_sensor = &reading;             /* reading dies when we return */
}

int main(void)
{
    bind_sensor();
    printf("%d\n", *g_sensor);       /* use after the stack frame is gone */
    return 0;
}

Plain GCC compiles this without a peep. AddressSanitizer will not be fooled, provided you switch on its stack-use-after-return detection:

$ gcc -g -fsanitize=address -o dbad ep_dangling_bad.c
$ ASAN_OPTIONS=detect_stack_use_after_return=1 ./dbad
==308==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f9323b00020 ...
READ of size 4 at 0x7f9323b00020 thread T0
    #0 0x55c9e00bd3a5 in main ep_dangling_bad.c:14

Why “it worked on my desk” proves nothing

Without the sanitizer this program usually prints 42, because nothing has overwritten that stack slot yet. Insert a printf or another function call before the read and the answer changes. Undefined behaviour is allowed to look correct.

Dangling Pointers in Embedded Systems

Firmware raises the stakes. A few real situations, all common in embedded C programming:

  • Starting a DMA transfer from a local buffer. The function returns, the stack frame is reused, and the DMA engine keeps reading the old address. The bytes on the wire are nonsense.
  • Registering a callback context. You pass &ctx where ctx is a local struct, then the callback fires later from an interrupt.
  • Handing a message to an RTOS queue by pointer. If the sender’s local buffer is gone by the time the receiver runs, the receiver reads stale data.
  • Freeing a buffer in one task while another still holds the pointer. Classic use-after-free across task boundaries.

On a microcontroller there is often no memory protection. A dangling write silently corrupts another variable instead of crashing, which makes the bug far harder to find than on a desktop.

Common Mistakes and Troubleshooting

SymptomLikely causeWhat to check
Value changes after an unrelated function callPointer to a dead localSearch for return &local and for stores of &local into globals
Crash only in release buildsOptimiser reuses the stack slot soonerBuild debug with -fsanitize=address
Random corruption after free()Second pointer still aliases the blockSet every alias to NULL, or own the block in one place only
Garbage in a transmitted frameDMA source was a stack bufferMove the buffer to static or global storage
Do not “fix” this by making the pointer volatile or by adding a delay. The object is gone. The only real fixes are to extend the object’s lifetime or to stop keeping its address.

Best Practices

  • Compile with -Wall -Wextra and treat -Wreturn-local-addr as an error.
  • Prefer caller-provided buffers. The caller decides the lifetime, so nothing can dangle.
  • After free(p), immediately assign p = NULL.
  • Never store the address of a local in anything that outlives the function: globals, structs, queues, callback contexts.
  • Run your host-side unit tests under AddressSanitizer. Add static analysis such as cppcheck or clang-tidy to CI.
  • In firmware, give DMA and queue buffers static storage with a clear owner.

Performance and Security Considerations

Caller-provided and static buffers cost nothing at run time, while heap allocation adds latency, fragmentation risk and a failure path you must handle. On the security side, returning a stack address is catalogued as CWE-562 and use after free as CWE-416. Attackers can sometimes steer what ends up in the reused memory, so these are not just reliability bugs.

Suggested Images

Image ideaALT text
Stack frame shrinking after function returnDangling pointer in C pointing at a released stack frame
Terminal showing GCC return-local-addr warningGCC warning for returning address of local variable in C
AddressSanitizer stack-use-after-return reportAddressSanitizer catching a dangling pointer in embedded C code

Summary and Key Takeaways

Remember

  • A dangling pointer keeps an address after its object is destroyed. A wild pointer was never initialised.
  • A local variable is valid only until its function returns.
  • Return values, fill a caller’s buffer, use static storage, or allocate deliberately. Never return a local’s address.
  • GCC warns about the obvious case. AddressSanitizer finds the global-pointer case.
  • In firmware, dangling pointers hide inside DMA, ISR and queue code.

Conclusion

Pointer bugs rarely announce themselves. A dangling pointer in C compiles, runs, and often passes the first test, which is exactly why you need a habit of asking “who owns this memory and how long does it live?” every time an address crosses a function boundary. Practise the four safe patterns until they are automatic. In the next lecture we look at how pointers are passed to functions and why a copy of a pointer cannot move the original.

FAQ: Dangling Pointers in C

What is a dangling pointer in C?

A pointer that holds the address of memory that has been freed or has gone out of scope. Using it is undefined behaviour.

Is a dangling pointer the same as a NULL pointer?

No. A NULL pointer holds a known invalid address and is easy to test for. A dangling pointer holds an address that looks valid but is not.

Can I return a pointer to a local array?

Not safely. The array disappears with the function. Return a pointer to a static array, to caller-owned storage, or to heap memory you document as caller-freed.

Does setting a pointer to NULL after free fix everything?

It protects that one pointer. Other pointers to the same block still dangle, so keep a single owner for each allocation.

How do I detect dangling pointers in embedded projects?

Test your logic on a PC build with AddressSanitizer and static analysis, review every place an address is stored long term, and keep DMA and queue buffers in static storage.

Keep Going With the Free C Course

Continue through the pointers chapter of our free embedded C course and build the habits that prevent firmware bugs before they ship.

PREV_LEC NEXT_LEC

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *