What Is a Segmentation Fault and What Causes It?

A segmentation fault (often shortened to “segfault”) is a crash that happens when a program tries to access a memory location it isn’t allowed to touch. The operating system detects the illegal access, kills the program immediately, and typically displays an error like SIGSEGV: Segmentation fault - invalid memory reference. If you’ve encountered one while writing or running code, you’re dealing with one of the most common and sometimes frustrating errors in programming.

How Memory Protection Works

Your operating system divides memory into segments and assigns each program its own protected space. Within that space, different regions have different permissions: some are readable and writable, some are read-only, and some are entirely off-limits. The hardware enforces these boundaries. When your program generates a memory address, the system checks whether that address falls within a valid, permitted region. If the address is out of bounds or the operation violates the region’s permissions (like trying to write to a read-only area), the hardware flags the violation.

On Linux and Unix systems, the kernel responds by sending signal number 11, called SIGSEGV, to the offending process. The default behavior is to terminate the program and produce a core dump, a snapshot of the program’s memory at the moment it crashed. That core dump is what makes debugging possible after the fact.

The Most Common Causes

Segfaults almost always trace back to a handful of programming mistakes, especially in languages like C and C++ where you manage memory manually.

  • Dereferencing a null pointer. A null pointer doesn’t point to any valid memory. If your code tries to read or write through one, the program immediately crashes. This is probably the single most common cause of segfaults.
  • Array out-of-bounds access. Arrays in C have fixed sizes, and the language doesn’t check whether your index is valid. Accessing index 100 of a 10-element array means your program is reading or writing memory it doesn’t own.
  • Using uninitialized pointers. When you declare a pointer without assigning it a value, it contains whatever garbage data happened to be in that memory location. If your code treats that garbage as a real address and tries to follow it, the result is unpredictable and often a segfault.
  • Incorrect pointer arithmetic. C lets you do math on pointers to navigate through memory. A miscalculated offset can land you in an entirely different region of memory, or outside your program’s allocated space entirely.
  • Writing to read-only memory. String literals and certain constants are stored in read-only segments. Attempting to modify them triggers a fault even though the address itself is technically valid.
  • Stack overflow. Every function call consumes stack space. If your program recurses too deeply (or infinitely), it exhausts the stack and starts accessing memory beyond the stack’s boundary. As one concise explanation puts it: “Stack overflow is the cause, segmentation fault is the result.”

Stack Overflow vs. Heap Corruption

The stack and the heap are two distinct regions of memory your program uses, and problems in either one can produce a segfault, though the mechanisms differ.

The stack holds local variables and keeps track of which function called which. It has a fixed size. Deep or runaway recursion piles up function calls until the stack runs out of room, and the next write lands in memory the program never requested from the operating system. The system sees an unauthorized access and sends SIGSEGV. On some architectures and compilers, you’ll get an explicit “stack overflow” error instead, but on many systems the crash simply shows up as a segmentation fault.

Heap corruption is sneakier. The heap is where dynamically allocated memory lives (anything you create with malloc in C or new in C++). Writing past the end of a heap-allocated buffer, freeing the same block twice, or using memory after it’s been freed can all corrupt the heap’s internal bookkeeping. The crash might not happen at the moment of the mistake. It might happen much later, when a completely unrelated part of your code tries to allocate or free memory and finds the bookkeeping data destroyed. This delayed effect makes heap-related segfaults notoriously difficult to track down.

Segmentation Faults vs. Page Faults

These two terms sound similar but refer to very different events. A page fault is a normal, expected part of how virtual memory works. The operating system doesn’t keep every page of your program’s memory in physical RAM at all times. When your program accesses a page that’s currently on disk, the hardware raises a page fault, the OS loads the page into RAM, and your program continues without ever knowing the interruption happened. This is routine and invisible.

A segmentation fault, by contrast, means the address itself is invalid or forbidden. There’s no page to load because the program was never supposed to access that location. The OS doesn’t recover from this; it terminates the process.

How to Find the Crash

Two tools handle the vast majority of segfault debugging in C and C++: GDB and Valgrind.

GDB (the GNU Debugger) lets you run your program under supervision. When the segfault occurs, GDB pauses execution at the exact line of code responsible and shows you the call stack, so you can see not just where the crash happened but the chain of function calls that led there. You can inspect variable values, examine memory contents, and step through code one line at a time. To use it, compile your code with the -g flag (which includes debugging information) and run gdb ./yourprogram.

Valgrind takes a different approach. It runs your program in a simulated environment and watches every single memory access. It catches problems GDB might miss: reading uninitialized memory, accessing freed memory, buffer overflows, and memory leaks. These issues don’t always cause an immediate crash, but Valgrind flags them anyway. You can even combine the two tools by launching Valgrind with --vgdb=yes --vgdb-error=0 and connecting GDB to it, giving you Valgrind’s memory analysis with GDB’s interactive debugging.

For quick triage, the core dump file your OS generates on a crash can be loaded into GDB after the fact with gdb ./yourprogram core, letting you examine the state of the program at the moment it died without needing to reproduce the crash.

Why Some Languages Don’t Have Them

Segmentation faults are overwhelmingly a C and C++ problem. In these languages, the programmer is directly responsible for allocating, using, and freeing memory. Every pointer is a potential source of error.

Languages like Python, Java, Go, and C# manage memory automatically through garbage collection. The runtime periodically identifies memory that’s no longer reachable by any part of the program and frees it. You never handle raw pointers, so you can’t dereference a null pointer or accidentally write past the end of an array. Java, for instance, throws an ArrayIndexOutOfBoundsException instead of letting you silently corrupt memory. The tradeoff is a small runtime performance cost for the garbage collector’s work.

Rust takes a different path entirely. Instead of cleaning up memory at runtime, Rust’s compiler enforces a set of ownership rules that determine exactly when memory is allocated and freed. Every piece of data has a single owning variable, and when that variable goes out of scope, the memory is deallocated. The compiler checks these rules before the program ever runs, catching potential memory errors at compile time with zero runtime overhead. This gives Rust the performance characteristics of C and C++ without the segfault risk.

The scale of the problem these languages solve is significant. A 2019 Microsoft study found that 70% of all security vulnerabilities in their software stemmed from memory safety issues. Google reported a similar figure for serious security bugs in the Chromium browser project. These aren’t just crashes; buffer overflows and use-after-free errors are the entry points for real-world exploits. The Morris worm, one of the first major internet attacks, spread partly through a buffer overflow vulnerability, causing an estimated $100,000 to $10,000,000 in economic damage.