Educational Blog

How to Fix a Segmentation Fault in C

Practical steps to diagnose and fix segmentation faults in C programs.

A segmentation fault in C is not a mysterious runtime curse. It is the operating system stopping your program from touching memory it does not own, cannot read, or cannot write. The fix is usually not one big trick. It is a disciplined process: identify the bad access, make the failure reproducible, inspect the pointer or index that led there, and then harden the code so the same class of bug cannot come back.

If you want the shortest practical answer, it is this: check every pointer, every array bound, every lifetime assumption, and every function contract that deals with memory. In C, segmentation faults usually come from undefined behavior that stayed hidden until the program hit an address the hardware rejected.

What a segmentation fault actually means

A segfault is the result, not the root cause. The root cause is usually one of these:

  • Dereferencing a null or uninitialized pointer
  • Reading or writing past the end of an array
  • Using memory after it has been freed
  • Returning or storing a pointer to a local variable that no longer exists
  • Writing through a string literal or other read-only memory
  • Calling code with the wrong type or wrong function signature so the stack or registers get corrupted

The operating system raises the fault because the process violated memory protection rules. That is why the crash location can be misleading. The line that crashes is often just the first place the program notices damage that happened earlier.

Start with the symptom, then trace backward

When you are debugging a segfault, do not guess. Use the stack trace, the failing line, and the values of nearby variables to move backward from the crash site.

SignalWhat it usually suggestsFirst check
Crash on pointer dereferenceNull, dangling, or corrupted pointerPrint pointer value and ownership
Crash inside memcpy or strcpyBad source, bad destination, or bad lengthVerify sizes and termination
Crash after freeUse-after-free or double freeTrace allocation and release paths
Crash in a loopOut-of-bounds index or off-by-oneInspect loop limits carefully
Crash on returnStack corruption or invalid local pointer escapeReview returned pointers and buffer writes

A reliable workflow is:

  1. Reproduce the crash with the smallest input that still fails.
  2. Compile with warnings and debug symbols.
  3. Run under a debugger.
  4. Inspect the pointer or index that failed.
  5. Check all earlier assignments to that value.
  6. Add a guard, assert, or refactor so the invalid state cannot occur.

The most common causes and how to fix them

1. Null or uninitialized pointers

A pointer that was never set, or was set to NULL, cannot be dereferenced safely.

Example pattern:

int *p;
*p = 5;

This is invalid because p never points to a real int object. The fix is to initialize pointers immediately and ensure they refer to valid storage before use.

Better:

int value = 5;
int *p = &value;

If a pointer is optional, always check it before dereferencing it:

if (p != NULL) {
    *p = 5;
}

2. Array bounds violations

C does not protect arrays for you. If you write past the end, the program may appear fine for a while and then crash later.

Common mistake:

int arr[4];
for (int i = 0; i <= 4; i++) {
    arr[i] = i;
}

The loop should stop at i < 4, not i <= 4.

When debugging, verify both ends of the range:

  • Is the start index correct?
  • Is the end condition exclusive or inclusive?
  • Does the index come from user input or another calculation?
  • Can the index become negative?

3. Use after free

Memory obtained dynamically with malloc, calloc, or realloc must not be used after free.

The bug often looks like this:

char *buf = malloc(32);
free(buf);
buf[0] = 'a';

After free, the pointer still contains an address, but that address no longer belongs to your program. The fix is to avoid using the pointer and set it to NULL after freeing it when practical.

free(buf);
buf = NULL;

That does not solve every problem, but it makes accidental reuse easier to catch.

4. Double free

Freeing the same allocation twice can corrupt the allocator’s internal state and crash later.

Pattern to avoid:

free(ptr);
free(ptr);

A good rule is to define one owner for each allocation and one clear cleanup path for each function. If multiple branches can free the same pointer, centralize the cleanup or null the pointer after release.

5. Returning pointers to local variables

A local variable lives on the stack and disappears when the function returns.

Bad example:

int *make_value(void) {
    int x = 42;
    return &x;
}

That pointer becomes invalid immediately. The fix is to allocate memory dynamically, return by value, or let the caller provide storage.

6. Writing into string literals

String literals are often stored in read-only memory.

char *s = "hello";
s[0] = 'H';

If you need to modify the text, store it in a writable array instead:

char s[] = "hello";
s[0] = 'H';

Debugging tools that save time

You can fix many segmentation faults faster with tools than by reading code line by line.

  • gdb or lldb for stack traces, breakpoints, and variable inspection
  • AddressSanitizer for out-of-bounds access and use-after-free
  • UndefinedBehaviorSanitizer for invalid arithmetic and other UB patterns
  • Compiler warnings with -Wall -Wextra -Wpedantic to catch suspicious code early

A strong compile line for debugging is:

gcc -g -O0 -Wall -Wextra -Wpedantic -fsanitize=address,undefined yourfile.c -o yourprogram

That combination often turns a vague crash into a precise diagnostic.

A practical checklist for fixing the bug

Use this checklist when you hit a segfault:

  • Confirm the exact line where the crash occurs
  • Identify every pointer used on that line
  • Check whether each pointer is initialized
  • Check whether any object has already been freed
  • Verify array sizes, indices, and loop bounds
  • Check for NULL before dereference when the pointer can legitimately be empty
  • Review recent changes for new lifetime or ownership mistakes
  • Run with sanitizers and warnings enabled

If the crash only happens sometimes, the bug is often a hidden memory corruption. The actual mistake may be several function calls earlier.

How to prevent the same class of bug

A good C codebase does not rely on memory discipline alone. It creates guardrails.

Use explicit ownership

Every dynamically allocated object should have a clearly defined owner. Document which function allocates, which function frees, and whether a pointer is borrowed or owned.

Prefer small functions

Long functions make it harder to see where values are initialized, modified, and released. Smaller functions make invalid states easier to spot.

Validate input early

If a function accepts a size, index, pointer, or string, validate it at the boundary before the value is used deeper in the program.

Avoid raw pointer arithmetic unless necessary

Pointer arithmetic is legal in C, but it makes off-by-one mistakes easier to create and harder to read. Prefer index-based access when possible.

Keep tests that cover edge cases

Test zero-length inputs, empty arrays, maximum lengths, null-like values, and repeated cleanup paths. Many segfaults only show up at the edges.

A simple mental model

When you are staring at a crash, ask these three questions:

  1. What memory is this code trying to access?
  2. Who owns that memory right now?
  3. Is the access inside the valid range and valid lifetime?

If you cannot answer all three, you have found a likely defect.

When the fix is not local

Sometimes the crashing line is innocent. In that case, the real issue is upstream:

  • A function returned a pointer that outlived its storage
  • A buffer length was computed incorrectly
  • A struct was partially initialized
  • A caller passed the wrong object type
  • Memory was already corrupted by a previous write

This is why sanitizers and debugger breakpoints matter. They help you catch the first invalid action instead of the final crash.

Quick reference

ProblemSafe response
Null pointerInitialize it or check before use
Out-of-bounds accessFix loop bounds and length calculations
Use after freeRemove reuse and clarify ownership
Double freeFree once, then null or centralize cleanup
Returned local addressReturn by value or use heap storage
Read-only writeUse writable storage instead

Bottom line

To fix a segmentation fault in C, stop treating the crash as the problem and treat it as evidence. Start at the failing instruction, trace the pointer or index backward, confirm ownership and bounds, and then add guardrails so the bug cannot return. Most segfaults are not random. They are the predictable result of a memory contract being broken somewhere earlier in the program.

Written by

c-double.com Editorial Team

Editorial team

c-double.com publishes practical how-to guides and educational articles with clear steps and useful context.