What you'll learn
Quick Answer
A segmentation fault means your program touched memory it does not own, so the operating system killed it. The usual causes are dereferencing a null or uninitialised pointer, indexing past the end of an array, using memory after freeing it, stack overflow from runaway recursion, writing to a string literal, and returning a pointer to a local variable. Compile with -g -fsanitize=address and the crash reports the exact line and the reason.
What a Segfault Actually Is
Every process gets its own region of memory. The operating system enforces the boundary. When your program reads or writes outside what it legitimately owns, the OS terminates it immediately with SIGSEGV.
Segmentation fault (core dumped)That is the entire message — no line number, no variable name. It is a protection mechanism firing, not a compiler diagnostic, which is why it feels so unhelpful.
The important consequence: the crash location is not always the bug location. Writing past the end of an array may corrupt memory that is only used much later, so the program dies somewhere entirely unrelated. This is why adding print statements can be misleading — the corruption already happened.
It is also why a segfault can appear intermittently. Whether out-of-bounds memory happens to be mapped decides whether you crash or silently corrupt data, and that can vary between runs, machines and compilers. A program that "works on my machine" and segfaults on the judge is usually this.
Causes 1 to 3: Pointers
Dereferencing a null pointer. The classic.
int* p = nullptr;
*p = 42; // segfault
Node* head = nullptr;
std::cout << head->data; // segfault — very common in linked list codeAlways check before dereferencing anything that might be null, especially when walking a linked structure.
Uninitialised pointers. Worse than null, because the value is whatever rubbish was in that memory.
int* p; // not initialised — holds garbage
*p = 42; // writes to a random addressSometimes this crashes, sometimes it silently corrupts something. Always initialise: int* p = nullptr;.
Use after free. The pointer still holds the old address, but the memory no longer belongs to you.
int* p = new int(5);
delete p;
std::cout << *p; // undefined behaviour — may print, may crash
delete p; // double delete — also undefined behaviourSet the pointer to nullptr after deleting, or better, stop managing raw memory by hand. std::unique_ptr and std::shared_ptr free automatically and cannot be double-deleted, which removes this whole category.
Causes 4 to 6: Bounds, Stack and Literals
Array out of bounds. C++ does not check indexes.
int arr[5];
arr[10] = 1; // no error, no warning — writes past the array
std::vector<int> v(5);
v[10] = 1; // operator[] does NOT bounds check either
v.at(10) = 1; // at() DOES — throws std::out_of_rangeThe at() distinction is worth internalising: it converts silent memory corruption into a catchable exception with a clear message. Use it while developing.
Off-by-one in loops is the usual trigger — for (int i = 0; i <= n; i++) touches one element past the end.
Stack overflow from recursion. Every call consumes stack space, and the stack is finite.
int f(int n) { return f(n - 1); } // no base case — segfault, not a crash messageDeep but correct recursion can also overflow. If depth may reach hundreds of thousands, convert to an iterative solution with an explicit stack.
Writing to a string literal. Literals live in read-only memory.
char* s = "hello"; // deprecated: points into read-only memory
s[0] = 'H'; // segfault
char s[] = "hello"; // correct: a modifiable copy on the stack
s[0] = 'H'; // fine
Finding the Line in Under a Minute
Do not guess. Three tools will tell you exactly where and why.
AddressSanitizer is the fastest option and should be your default while developing. It is built into g++ and clang++.
g++ -g -fsanitize=address -fno-omit-frame-pointer main.cpp -o main
./mainInstead of "segmentation fault" you get the offending line, the kind of error, and the allocation it relates to:
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
WRITE of size 4 at 0x... thread T0
#0 0x... in main main.cpp:12
0x... is located 0 bytes to the right of 40-byte region
allocated by thread T0 here:
#0 0x... in operator new[]gdb when you need to inspect state at the moment of the crash:
g++ -g main.cpp -o main
gdb ./main
(gdb) run
(gdb) backtrace # the call chain that led here
(gdb) print p # inspect any variableValgrind catches leaks and uninitialised reads that sanitizers may miss:
valgrind --leak-check=full ./mainThe -g flag is essential in all cases — without debug symbols you get addresses instead of line numbers.
Writing Code That Does Not Segfault
Modern C++ exists largely to remove these failure modes. Using it is more effective than debugging harder.
- Prefer
std::vectorandstd::stringto raw arrays andchar*. They manage their own memory and know their size. - Use smart pointers.
std::unique_ptrandstd::shared_ptrfree automatically, cannot be double-deleted, and make ownership explicit. - Use range-based for loops.
for (int x : v)cannot go out of bounds because there is no index to get wrong. - Initialise everything at declaration. Uninitialised pointers and variables are a large share of intermittent crashes.
- Never return a pointer or reference to a local. The variable is destroyed when the function returns, leaving a dangling pointer. Return by value — modern compilers optimise the copy away.
- Turn on warnings.
-Wall -Wextracatches many of these at compile time, before they ever run.
g++ -g -Wall -Wextra -fsanitize=address,undefined main.cpp -o mainMake that your standard development command. The undefined-behaviour sanitizer also catches integer overflow and other silent problems that produce wrong answers rather than crashes.
