C Segmentation Fault

  1. Program Segments in C
  2. Dynamic Memory in C
  3. Segmentation Fault in C
  4. Conclusion
C Segmentation Fault

This tutorial will discuss the segmentation fault in C and show some code examples to explain the reason for this error. First, we will talk about program segments and dynamic memory.

Later, we will explore different reasons for the segmentation fault and possible solutions.

Program Segments in C

Computer memory is divided into primary memory and secondary memory. It must load every program in primary memory (RAM) to execute the program.

A program is further divided into different segments.

There are five main segments of a program. These segments are:

  1. Text Segment - The text segment contains the program’s code.

  2. Initialized data segment - The initialized data segment contains global initialized variables of the program.

  3. Uninitialized data segment - The uninitialized data segment contains global uninitialized variables of the program. This section is also called bss (better save space).

    Sometimes, we declare a large uninitialized array like int x[1000], which requires 4000 bytes; however, this space is not required until the array is initialized.

    Therefore, this space is not reserved; only a pointer is saved. The memory is allocated when an array is initialized.

  4. Heap - The heap section contains data for which memory is requested at run time because the exact size is unknown to the programmer at the coding time.

  5. Stack - The stack section contains all the local variables. Whenever a function is called, its local variables are pushed onto the stack (so the stack grows), and when the function returns, the local variables are popped (the stack shrinks).

To visualize and develop a better understanding of program segments, see the following code:

int x = 5;
int y[100];
int main() {
  int number = 5;
  int *x = new int[5];
  return 0;
}

In this program, x is global initialized data, and y is global uninitialized data. Next, the number is a local variable; go to a stack area.

x is a pointer, again a local variable, go to a stack area. The new int[5] allocates space to the heap area.

In the Unix family of operating systems, you can easily see the segments of this program.

Program Segments in C

Dynamic Memory in C

In many programs, programmers don’t know the exact memory requirement. In such cases, the programmer either takes input from the user or file to get the data size and declares memory at run time according to the input.

See an example:

int main() {
  int size;
  cout << "Enter Size: ";
        cin >> size
	int *x = (int*) malloc(size] * sizeof(int) );
        ... return 0;
}

In this program, the user inputs the size, and the program allocates memory according to the size at run time.

In a multiprogramming environment, an operating system needs to provide memory protection. That is to restrict programs from sharing data without their willingness.

Therefore, every operating system keeps some mechanism to stop programs from accessing illegal memory.

We can only access the memory which is reserved for our program. A segmentation fault may occur if we try to access addresses outside the program’s address space or if the program’s allocated memory is insufficient to fulfill the dynamic allocation requests.

Let’s discuss this in detail.

Segmentation Fault in C

A segmentation fault occurs when you try to access the memory location beyond your program’s reach or don’t have permission to access the memory. Let us discuss some cases below.

Try to Deference an Uninitialized Pointer

This error can be confusing because some compilers give warnings for some cases and help you avoid this error, while others don’t. Below is an interesting example that is confusing.

int main() {
  int* pointer;
  printf("%d\n", *pointer);
  return 0;
}

In the above code, we are trying to dereference an unallocated pointer which means trying to access the memory for which we don’t have access permission.

Compiling this program with (cygwin) GCC version 9.3.0 gives the segmentation fault (core dumped).

If we compile it with g++ 9.3.0, then it prints zero.

Now, if we change this program a little bit and add a function:

void access() {
  int* pointer;
  printf("%d\n", *pointer);
}
int main() {
  access();
  return 0;
}

Now both compiles print garbage value as an output which is confusing because we are still trying to access the unallocated memory.

If we try this on any online compiler, it has similar behavior. Segmentation fault is abnormal; sometimes, making a small change can add or remove this error.

To avoid this kind of error, remember to initialize your pointer, and before dereferencing, check if the pointer is not null.

Try to Allocate Large Memory

This error can come in two ways. One when you declare a large size array on the stack and the second when you declare large memory on the heap.

We will see both one by one.

#include <stdio.h>

int main() {
  int largeArray[10000000];  // allocating memory in stack
  printf("Ok\n");
  return 0;
}

If you reduce the number of zeros, the output will be Ok; however, if you keep on increasing the zeros, at some point, the program will crash and give this:

Segmentation fault

The reason is that the stack area is finite. This means the memory required by this large array is unavailable.

Eventually, your program is trying to go out of the segment.

We may use a heap if we need more memory (larger than available on the stack). However, the heap also has limits; therefore, if we keep increasing the memory size, there will be errors.

See the example below.

#include <stdio.h>
#include <stdlib.h>
int main() {
  int *veryLargeArr;
  long int size = 100000000000;
  veryLargeArr = (int *)malloc(sizeof(int) * size);
  if (veryLargeArr == NULL)
    printf("Space is not enough.\n");
  else
    printf("memory allocation is successful\n");
  return 0;
}

Output:

memory allocation is successful

However, if we keep increasing the size, the limit will exceed. For example, we got an issue with the following size:

long int size = 1000000000000000;  // 100000000000

You may count more zeros in the above statement. In such a case, the program may crash; however, to avoid a segmentation fault, we have checked if the pointer is NULL or not.

NULL means no memory was allocated because the requested space is unavailable.

Output:

Space is not enough.

If you try this code using dynamically allocated memory without checking, you will come up with a segmentation fault.

Infinite Loop or Recursive Call

If you mistakenly leave an infinite loop in your program, that will cause the segmentation fault, especially if you allocate dynamic memory inside the loop.

An example of having an infinite loop with dynamic memory allocation is given.

#include <stdio.h>
#include <stdlib.h>
int main() {
  int *p;
  while (true) p = (int *)malloc(100000);
  return 0;
}

Here, you can see an infinite loop using while (true). The memory allocation statement inside the loop will ultimately generate an error because memory allocation is repeated repeatedly without calling the free method to free the memory.

Similarly, creating a recursive function without adding a base case can also cause the stack to overflow. See the example below.

void check() { check(); }

int main() { check(); }

In the code above, the check function will keep calling itself and creating its copies on the stack which will cause the segmentation fault once the available memory for the program is consumed.

Conclusion

A segmentation fault occurs when a program tries to access a memory that is beyond its reach or not available. Check if the pointer is pointing to any memory before dereferencing.

Use dynamic memory if a large space is required and check if the pointer is NULL or not. Make sure to use & before a variable in scanf and use the correct specifier after % in printf.

Don’t try to assign or access any value from an array outside its size. Always initialize the variables and the pointers whenever at the time of declaration.

Related Article - C Error