Learn what causes segmentation faults in C programming, how to debug invalid memory access, and step-by-step techniques to prevent core dumps.
A Segmentation Fault (frequently abbreviated as Segfault or output as Segmentation fault (core dumped)) is one of the most frustrating errors you will encounter when writing C programs. Unlike compile-time syntax errors where GCC or Clang pinpoint exact line numbers, a segmentation fault occurs at runtime when your executable attempts to access a memory location that it does not have permission to read or write.
When a illegal memory access occurs, the hardware Memory Management Unit (MMU) notifies the operating system, which immediately sends a termination signal (SIGSEGV, Signal #11) to your process. The program halts instantly to prevent memory corruption or security breaches.
In this comprehensive guide, we will explore the low-level hardware mechanics behind segmentation faults, walk through the 6 most common causes with code examples and fixes, and examine professional debugging tools like GDB, AddressSanitizer, and Valgrind.
Understanding Virtual Memory and the MMU
To effectively diagnose why segmentation faults happen, you must understand how modern operating systems manage application memory.
When your C program runs, the operating system does not grant it direct access to raw physical RAM chips. Instead, it provides a contiguous Virtual Address Space partitioned into distinct logical segments:
- Text Segment (Code): Contains read-only compiled machine instructions.
- Data & BSS Segments: Store initialized and uninitialized global and static variables.
- Heap Segment: Used for dynamic memory allocations (
malloc,calloc,realloc) managed at runtime. - Stack Segment: Stores local function variables, call parameters, and return stack frames.
text (ISO Standard)+-----------------------------------+ High Memory Addresses (0x7FFF...) | Stack Segment | (Grows Downward) | (Local variables, return addrs) | | | | | v | | | | ^ | | | | | Heap Segment | (Grows Upward) | (Dynamic malloc / free memory) | +-----------------------------------+ | Data & BSS Segments | (Global & Static variables) +-----------------------------------+ | Text Segment | (Executable code - Read Only) +-----------------------------------+ Low Memory Addresses (0x0004...) | Unmapped / Null Page | (0x00000000 - Protected NULL) +-----------------------------------+ Address 0x0
Every page of virtual memory has permission bits: Read (R), Write (W), and Execute (X).
A segmentation fault is triggered by the Memory Management Unit (MMU) under three specific hardware conditions:
- Permission Violation: Attempting to write data to a read-only memory segment (such as modifying string literals in the Text Segment).
- Unmapped Address Access: Attempting to read or write to a memory address that does not exist in your process address space (such as address
0x0orNULL). - Kernel Space Access: Attempting to read memory belonging to the OS kernel or another isolated user process.
6 Most Common Causes of Segmentation Faults in C
Let us inspect the six most common coding mistakes that lead to segmentation faults, complete with vulnerable code snippets and step-by-step corrections.
Cause 1: Dereferencing NULL or Uninitialized Pointers
The single most frequent cause of segmentation faults among C beginners is attempting to dereference a pointer that contains NULL or garbage uninitialized address data.
Vulnerable Code Example
c (ISO Standard)#include <stdio.h> #include <stdlib.h> int main(void) { int *ptr = NULL; // BAD: Dereferencing NULL address (0x0) printf("Value: %d\n", *ptr); return 0; }
When the CPU evaluates *ptr, it attempts to read 4 bytes starting at virtual address 0x00000000. Because address zero is intentionally kept unmapped by the operating system, the MMU fires an immediate hardware trap, causing Segmentation fault (core dumped).
The Fix: Always Validate Pointers Before Dereferencing
c (ISO Standard)#include <stdio.h> #include <stdlib.h> int main(void) { int *ptr = NULL; // GOOD: Explicit NULL validation check if (ptr != NULL) { printf("Value: %d\n", *ptr); } else { printf("Error: Pointer is NULL, skipping dereference.\n"); } return 0; }
Similarly, when using dynamic memory allocation functions like malloc(), always check if the allocator returned NULL due to heap exhaustion before accessing the memory block.
Cause 2: Array Out-of-Bounds Access
Unlike languages like Java, Rust, or Python, standard C does not perform automatic array boundary checks at runtime. Reading or writing beyond array bounds can overwrite adjacent stack frames or access unmapped virtual memory pages.
Vulnerable Code Example
c (ISO Standard)#include <stdio.h> int main(void) { int numbers[5] = {10, 20, 30, 40, 50}; // BAD: Loop runs 100 times on an array of size 5 for (int i = 0; i < 100000; i++) { numbers[i] = i * 2; // Will eventually hit an unmapped memory page } return 0; }
The Fix: Enforce Strict Loop Boundaries
c (ISO Standard)#include <stdio.h> #define ARRAY_SIZE 5 int main(void) { int numbers[ARRAY_SIZE] = {10, 20, 30, 40, 50}; // GOOD: Loop condition bounded strictly to array length for (int i = 0; i < ARRAY_SIZE; i++) { numbers[i] = i * 2; printf("numbers[%d] = %d\n", i, numbers[i]); } return 0; }
Cause 3: Forgetting the & Address-Of Operator in scanf()
When calling scanf(), you must pass the address of the variable where user input should be stored. If you omit the & operator, scanf() interprets the current raw integer value of the variable as a memory pointer address!
Vulnerable Code Example
c (ISO Standard)#include <stdio.h> int main(void) { int age; // Uninitialized garbage value (e.g., 0 or 4198402) printf("Enter your age: "); // BAD: Missing '&' operator before age scanf("%d", age); printf("You entered: %d\n", age); return 0; }
The Fix: Pass Pointer Addresses to scanf()
c (ISO Standard)#include <stdio.h> int main(void) { int age = 0; printf("Enter your age: "); // GOOD: Pass memory address &age if (scanf("%d", &age) == 1) { printf("You entered: %d\n", age); } else { printf("Invalid input received.\n"); } return 0; }
Cause 4: Writing to Read-Only String Literals
In C, string literals defined with double quotes (e.g., "Hello World") are stored in the read-only section of the Text Segment. Attempting to modify characters inside a string literal triggers a write-permission violation.
Vulnerable Code Example
c (ISO Standard)#include <stdio.h> int main(void) { // BAD: Points directly to read-only Text Segment memory char *str = "Hello World"; // Modifying read-only memory causes SIGSEGV str[0] = 'h'; return 0; }
The Fix: Use Mutable Character Arrays on the Stack
c (ISO Standard)#include <stdio.h> int main(void) { // GOOD: Copies string into a mutable character array on the Stack char str[] = "Hello World"; str[0] = 'h'; // Completely safe printf("Modified string: %s\n", str); // Outputs: hello World return 0; }
Cause 5: Use-After-Free & Dangling Pointers
When dynamic memory allocated via malloc() is released back to the system using free(), the pointer variable still holds the original memory address. Accessing memory through a freed pointer is known as a Use-After-Free flaw, leading to undefined behavior or segmentation faults.
Vulnerable Code Example
c (ISO Standard)#include <stdio.h> #include <stdlib.h> int main(void) { int *buffer = (int *)malloc(10 * sizeof(int)); if (!buffer) return 1; buffer[0] = 42; free(buffer); // Heap memory returned to system // BAD: Accessing freed heap pointer (Dangling Pointer) printf("Buffer[0]: %d\n", buffer[0]); return 0; }
The Fix: Reset Pointers to NULL Immediately After Freeing
c (ISO Standard)#include <stdio.h> #include <stdlib.h> int main(void) { int *buffer = (int *)malloc(10 * sizeof(int)); if (!buffer) return 1; buffer[0] = 42; free(buffer); buffer = NULL; // GOOD: Reset pointer to NULL to prevent accidental reuse return 0; }
Cause 6: Stack Overflow via Deep or Infinite Recursion
The Stack segment allocated for a C process is usually capped at 8 MB on Linux and macOS systems. If a recursive function fails to hit its base case, stack frames accumulate until stack memory overflows into unmapped guard pages, triggering a SIGSEGV.
Vulnerable Code Example
c (ISO Standard)#include <stdio.h> void infiniteRecursion(int count) { printf("Frame: %d\n", count); // BAD: Missing base case leads to stack overflow segfault infiniteRecursion(count + 1); } int main(void) { infiniteRecursion(1); return 0; }
The Fix: Always Guarantee a Reachable Base Case
c (ISO Standard)#include <stdio.h> void safeRecursion(int count) { // GOOD: Explicit base termination condition if (count > 10) { return; } printf("Frame: %d\n", count); safeRecursion(count + 1); } int main(void) { safeRecursion(1); return 0; }
3 Professional Debugging Tools for Segmentation Faults
When a segmentation fault occurs in a large C codebase, manual printf() debugging is slow and unreliable. Industry-standard developers rely on diagnostic tooling.
1. GCC AddressSanitizer (ASan)
AddressSanitizer is a fast memory error detector built into GCC and Clang. It detects out-of-bounds access, use-after-free, use-after-scope, and double-free errors with exact line numbers and memory dumps.
How to Compile and Run with AddressSanitizer:
bash (ISO Standard)gcc -fsanitize=address -g program.c -o program ./program
Example AddressSanitizer Diagnostic Output:
text (ISO Standard)================================================================= ==12345==ERROR: AddressSanitizer: global-buffer-overflow on address 0x... READ of size 4 at 0x... thread T0 #0 0x100003f24 in main program.c:8 #1 0x7fff20357310 in start (libdyld.dylib:x86_64) SUMMARY: AddressSanitizer: global-buffer-overflow program.c:8 in main =================================================================
ASan points directly to line 8 of program.c as the root cause of the invalid read.
2. GDB (GNU Debugger)
The GNU Debugger allows you to run your executable step-by-step and automatically pauses execution at the exact moment a SIGSEGV signal is generated.
Step-by-Step GDB Session:
-
Compile with Debug Symbols (
-g):bash (ISO Standard)gcc -g program.c -o program -
Launch GDB:
bash (ISO Standard)gdb ./program -
Run Program inside GDB:
text (ISO Standard)(gdb) run Starting program: ./program Program received signal SIGSEGV, Segmentation fault. 0x0000000000401142 in main () at program.c:6 6 printf("%d\n", *ptr); -
Inspect the Call Stack (
backtraceorbt):text (ISO Standard)(gdb) bt #0 0x0000000000401142 in main () at program.c:6 -
Print Variable Values:
text (ISO Standard)(gdb) print ptr $1 = (int *) 0x0
GDB immediately shows that ptr evaluated to address 0x0 (NULL) on line 6.
3. Valgrind Memcheck
Valgrind is a synthetic CPU emulator that tracks every memory read, write, and dynamic allocation in your application.
Running Valgrind:
bash (ISO Standard)gcc -g program.c -o program valgrind --leak-check=full --track-origins=yes ./program
Valgrind reports invalid reads, uninitialized values, and memory leaks with absolute precision.
Defensive C Programming Rules to Prevent Segfaults
To write reliable, production-ready C code, follow these five engineering rules:
| Rule | Description |
|---|---|
| 1. Pointer Initialization | Always initialize pointers to NULL or a valid memory address upon declaration (int *p = NULL;). |
| 2. Allocation Validation | Always verify that malloc(), calloc(), or realloc() return non-NULL pointers before dereferencing. |
| 3. Post-Free Nullification | Set pointers to NULL immediately after calling free(ptr) to convert dangerous dangling accesses into immediate detectable null pointer checks. |
| 4. Safe String Functions | Avoid unbounded string routines like strcpy() or gets(). Use safe variants like strncpy(), snprintf(), or fgets(). |
| 5. Enable Compiler Warnings | Compile with -Wall -Wextra -Wpedantic during development. GCC will flag potential missing & operators in scanf() and uninitialized variable uses at build time. |
Frequently Asked Questions (FAQ)
1. What signal number is a Segmentation Fault in Linux?
A segmentation fault corresponds to signal number 11 (SIGSEGV). It is defined in the standard <signal.h> header. You can catch or handle SIGSEGV using the sigaction() system call, though attempting to resume normal execution after invalid memory access is generally unsafe.
2. Why does printf() placed right before a crash not show up in the terminal?
In C, standard output (stdout) is line-buffered by default when connected to a terminal. If your program reaches a segmentation fault before stdout flushes its internal buffer, pending text is discarded when the process aborts. To ensure debugging statements appear before a crash, call fflush(stdout); immediately after printf(), or print to unbuffered stderr using fprintf(stderr, "Debug step 1\n");.
3. What is the difference between a Segmentation Fault (SIGSEGV) and a Bus Error (SIGBUS)?
Both indicate illegal memory access, but with different root causes:
SIGSEGV: The memory address is logically invalid or protected (e.g., accessing unmapped virtual memory address0x0or writing to read-only text segments).SIGBUS: The memory address is physically invalid or misaligned (e.g., attempting a 4-byte integer load from an odd byte address on architectures requiring strict alignment, or accessing a memory-mapped file beyond its actual file size).
4. Can a segmentation fault happen without explicitly using pointers?
Yes. Unbounded array accesses (arr[100000]), stack overflow from infinite recursion, or format specifier mismatches in printf("%s", 12345) all cause segmentation faults even if you never declared an explicit pointer variable (*ptr).
5. How do I enable and inspect Core Dump files on Linux?
By default, core file generation may be disabled (ulimit -c 0). To enable core dump generation:
bash (ISO Standard)ulimit -c unlimited ./my_crashing_program
Once a core or core.<pid> file is created, inspect it with GDB:
bash (ISO Standard)gdb ./my_crashing_program core (gdb) bt
Conclusion
Segmentation faults are not mysterious crashes—they are a protective hardware mechanism enforced by the operating system to maintain system integrity. By understanding virtual address spaces, enforcing boundary checks, initializing pointers, and leveraging modern diagnostic tools like AddressSanitizer and GDB, you can find and fix segmentation faults in minutes.
Related Technical Resources
- Master pointer mechanics in our Pointers in C Guide.
- Learn dynamic heap allocation in Dynamic Memory Allocation in C.