Diagnosing Null-Pointer Segmentation Faults in C Programs
A systematic guide to diagnosing null-pointer segmentation faults in C: reading the fault address, using GDB backtraces, matching fixes to causes, and knowing when to escalate to Valgrind or ASan.
31 Mar 2026, 04:13 UTC

Your C program dies with Segmentation fault (core dumped), and the only clue is a crash address near zero. That pattern almost always means a null-pointer dereference: the code tried to read or write through a pointer whose value is NULL. This guide walks through recognizing the condition, narrowing the cause, applying the right fix, and knowing when the problem is something worse.
Recognizing the condition
When a process dereferences NULL, the CPU traps on an access to an unmapped page and the kernel delivers SIGSEGV. The telltale signs:
- The shell reports
SIGSEGVor "Segmentation fault" with no other error message. - The faulting address is
0x0or a small value like0x8or0x20. Small non-zero offsets usually mean the code accessed a struct member through a null struct pointer (p->fieldwherepisNULLandfieldsits at offset 8). - A core dump is produced (if enabled) or the debugger stops at the faulting instruction.
If the faulting address is large, random-looking, or changes between runs, you are more likely dealing with memory corruption than a simple null dereference; skip to the escalation section.
Common causes and their clues
| Cause | Typical clue |
|---|---|
| Pointer declared but never initialized | Fault address may be zero or garbage; variable has no assignment before use |
malloc/calloc returned NULL, unchecked | Crash occurs under memory pressure or with large allocation requests |
Library call returned NULL (e.g., fopen, getenv, strchr) | Crash on the line immediately consuming the return value |
| Pointer used after being freed and set to NULL | Crash on a path that runs after cleanup or error handling |
| Function returned NULL on an error path the caller ignored | Crash only with specific failing inputs (missing file, unknown key) |
Ordered diagnostic checks
Build with debug symbols and no optimization first: gcc -g -O0 program.c -o program. Optimized builds can inline functions and reorder code, making the reported fault location misleading. Run these checks in order.
- Get the faulting address and backtrace. Run the program under GDB:
gdb ./program core(orgdb ./programthenrunwith your input). At the crash,btshows the call stack andinfo registersshows the faulting address. You need permission to read the core file; if cores are not produced, checkulimit -cand your system's core pattern. - Identify the exact source line. The top frame's file and line number point at the dereference. If the top frame is inside a library, walk up the stack (
up) until you reach your code. - Inspect the pointer. In GDB,
print ptrat the crash frame. If it shows0x0, trace backward:listthe surrounding code and find every assignment to that variable on the executed path. - Audit allocation and return-value checks. Search the path for
malloc,calloc,realloc, and any function documented to return NULL on failure. Confirm each result is tested before use.
Fixes matched to findings
Never initialized: initialize at declaration or guarantee assignment before first use.
struct config *cfg = NULL; /* explicit, so misuse faults predictably */
cfg = load_config(path);
if (cfg == NULL) {
fprintf(stderr, "failed to load config: %s\n", path);
return -1;
}
Unchecked allocation: test every allocation result and decide on a failure policy (abort cleanly, propagate an error, or retry) rather than dereferencing blindly.
char *buf = malloc(size);
if (buf == NULL) {
perror("malloc");
exit(EXIT_FAILURE); /* or return an error code to the caller */
}
Library return values: read the function's documentation for its failure convention, then validate. For example, getenv returns NULL when the variable is unset, and fopen returns NULL on failure with errno set.
Use-after-free with NULL reset: setting a pointer to NULL after free converts a subtle use-after-free into a detectable null dereference, but you still must guard every use. Better, restructure ownership so the pointer is not reachable after its lifetime ends.
Verifying the fix
Re-run the program under GDB with the same input that crashed and confirm it completes without SIGSEGV. Then rebuild with AddressSanitizer (gcc -g -fsanitize=address) and run your test suite; ASan reports null dereferences and related memory errors with precise locations. A static analyzer such as clang --analyze or cppcheck can flag dereferences that lack a preceding null check, catching paths your tests do not exercise.
When to escalate
Escalate beyond manual debugging when any of these hold:
- The faulting address is non-zero and not a small struct offset, suggesting a wild pointer from a buffer overflow or use-after-free.
- The backtrace shows corrupted frames (impossible return addresses,
??entries), indicating stack smashing. - The crash is intermittent or moves between runs, pointing at uninitialized memory or a race.
At that point, run under Valgrind (valgrind --track-origins=yes ./program) or AddressSanitizer with ASAN_OPTIONS=halt_on_error=1, and consider -fsanitize=undefined for undefined-behavior checks. These tools find the first invalid write rather than the later crash it causes.
Cautions
Avoid printf-style debugging inside signal handlers; most stdio functions are not async-signal-safe and can deadlock or corrupt output. Log from normal code paths instead. Also verify fixes in a debug build first: at -O2, the compiler may assume dereferenced pointers are non-null and delete your checks, so confirm behavior at both -O0 and your production optimization level.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.