Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
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.
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.
Dangling vs Wild vs NULL
| Kind | What it holds | Dereference result | Cure |
|---|---|---|---|
| NULL pointer | Address zero by convention | Usually a crash or hard fault, easy to detect | Check before use |
| Wild pointer | Never initialised, random bits | Undefined behaviour | Always initialise at declaration |
| Dangling pointer | Address of an object that is gone | Undefined behaviour, often looks fine at first | Control 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
ifor 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:
| Pattern | Who owns the memory | Embedded suitability |
|---|---|---|
Caller-provided buffer (read_into) | The caller | Best choice. No heap, predictable, easy to review. |
static local (read_static) | The program, for its whole life | Good, but not reentrant. Dangerous if an ISR and main both call it. |
Heap block (read_heap) | Caller must free | Often avoided in firmware. Many coding standards, MISRA C among them, restrict dynamic allocation. |
| Pointer into the caller’s own array | The caller | Excellent. 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
&ctxwherectxis 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
| Symptom | Likely cause | What to check |
|---|---|---|
| Value changes after an unrelated function call | Pointer to a dead local | Search for return &local and for stores of &local into globals |
| Crash only in release builds | Optimiser reuses the stack slot sooner | Build debug with -fsanitize=address |
Random corruption after free() | Second pointer still aliases the block | Set every alias to NULL, or own the block in one place only |
| Garbage in a transmitted frame | DMA source was a stack buffer | Move the buffer to static or global storage |
Best Practices
- Compile with
-Wall -Wextraand treat-Wreturn-local-addras an error. - Prefer caller-provided buffers. The caller decides the lifetime, so nothing can dangle.
- After
free(p), immediately assignp = 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 idea | ALT text |
|---|---|
| Stack frame shrinking after function return | Dangling pointer in C pointing at a released stack frame |
| Terminal showing GCC return-local-addr warning | GCC warning for returning address of local variable in C |
| AddressSanitizer stack-use-after-return report | AddressSanitizer 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.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments