A dangling pointer is a pointer that holds an address. Understanding why this happens requires knowing how the stack and heap work in C — stack variables are destroyed on function return, and heap memory is only valid until you call free()., but the memory at that address is no longer valid. Reading from it returns garbage. Writing to it corrupts memory silently. The crash, if you get one, usually happens somewhere else entirely — making this one of the harder bugs to track down.

Three Ways to Create a Dangling Pointer

1. Freeing heap memory and keeping the pointer

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

int main() {
    int *p = malloc(sizeof(int));
    *p = 42;

    free(p);
    /* p still holds the old address — but the memory is no longer ours */

    printf("%dn", *p);   /* dangling read — undefined behaviour */
    *p = 99;              /* dangling write — may corrupt allocator */

    return 0;
}

After free(p), the allocator may hand that memory to the next malloc call. If you write to p afterward, you are overwriting whatever the new owner put there. This can corrupt data structures, produce wrong results, or cause a crash much later in an unrelated part of the program.

Fix — set to NULL immediately after free:

free(p);
p = NULL;

/* Now any accidental use produces a segfault immediately */
printf("%dn", *p);   /* segfault on NULL — obvious and debuggable */

2. Returning the address of a local variable

#include <stdio.h>

int *make_number() {
    int x = 42;
    return &x;   /* x lives on the stack of make_number */
}              /* stack frame destroyed here */

int main() {
    int *p = make_number();
    printf("%dn", *p);   /* x no longer exists — dangling pointer */
    return 0;
}

Local variables live in the function’s stack frame. When the function returns, that frame is gone. The address you returned now points into stack memory that may be reused by the next function call.

GCC will warn about this with -Wall:

warning: function returns address of local variable [-Wreturn-local-addr]

Fix — allocate on the heap or pass a buffer from the caller:

/* Option 1: heap allocation (caller must free) */
int *make_number() {
    int *p = malloc(sizeof(int));
    if (p) *p = 42;
    return p;
}

/* Option 2: caller provides the storage */
void make_number(int *out) {
    *out = 42;
}

int main() {
    int x;
    make_number(&x);
    printf("%dn", x);   /* fine — x lives in main's stack frame */
    return 0;
}

3. Pointer to a variable that goes out of scope

#include <stdio.h>

int *get_pointer() {
    int *p;
    {
        int x = 42;
        p = &x;
    }   /* x goes out of scope here — p is now dangling */

    return p;   /* dangling */
}

int main() {
    int *p = get_pointer();
    printf("%dn", *p);   /* undefined behaviour */
    return 0;
}

Variables in inner blocks are destroyed when the block exits. Any pointer to them becomes dangling at that point.

Why the Bug Is Hard to Find

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

int main() {
    int *a = malloc(sizeof(int));
    int *b = malloc(sizeof(int));

    *a = 100;
    *b = 200;

    free(a);
    /* a is now dangling */

    *a = 999;   /* writing through dangling pointer */
    /* This may have just corrupted b's allocation metadata */

    printf("%dn", *b);   /* b may now print garbage or crash here */

    free(b);   /* may crash here due to corrupted metadata */
    return 0;
}

The write through the dangling pointer a corrupts the allocator’s internal linked list of free blocks. The crash shows up at free(b) or the next malloc — nowhere near the actual bug.

Detection Tools

AddressSanitizer

gcc -g -fsanitize=address -o program program.c
./program

AddressSanitizer catches use-after-free, use of stack memory after function return, and heap buffer overflows. It prints the exact line of the dangling access and the line where the memory was freed.

ERROR: AddressSanitizer: heap-use-after-free on address 0x...
READ of size 4 at 0x... thread T0
    #0 main program.c:12
freed by thread T0 here:
    #0 free program.c:9

Valgrind

valgrind --track-origins=yes ./program

Valgrind detects invalid reads and writes with similar precision. It is slower than AddressSanitizer but works on existing binaries without recompiling.

Defensive Patterns

/* Pattern 1: NULL after free */
free(p);
p = NULL;

/* Pattern 2: macro to free and nullify */
#define SAFE_FREE(p) do { free(p); (p) = NULL; } while (0)
SAFE_FREE(ptr);

/* Pattern 3: never store addresses of local variables */
/* Always allocate heap memory for data that outlives the creating scope */

TL;DR

  • A dangling pointer holds an address that is no longer valid
  • Three causes: free then use, return address of local, pointer outlives scope
  • Set every pointer to NULL immediately after freeing
  • Never return the address of a local variable — allocate on the heap or use output parameters
  • Compile with -fsanitize=address during development to catch these automatically
  • The crash rarely happens at the dangling access — it appears later, elsewhere