C 세분화 오류

Muhammad Sohail Iftikhar 2024년2월15일
  1. C의 프로그램 세그먼트
  2. C의 동적 메모리
  3. C의 분할 오류
  4. 결론
C 세분화 오류

이 자습서에서는 C의 세그먼테이션 오류에 대해 설명하고 이 오류의 원인을 설명하는 몇 가지 코드 예제를 보여줍니다. 먼저 프로그램 세그먼트와 동적 메모리에 대해 이야기하겠습니다.

나중에 세그멘테이션 결함에 대한 다양한 이유와 가능한 솔루션을 살펴보겠습니다.

C의 프로그램 세그먼트

컴퓨터 메모리는 주 메모리와 보조 메모리로 나뉩니다. 프로그램을 실행하려면 기본 메모리(RAM)의 모든 프로그램을 로드해야 합니다.

프로그램은 여러 세그먼트로 더 나뉩니다.

프로그램에는 다섯 가지 주요 부분이 있습니다. 이러한 세그먼트는 다음과 같습니다.

  1. 텍스트 세그먼트 - 텍스트 세그먼트는 프로그램의 코드를 포함합니다.

  2. 초기화 데이터 세그먼트 - 초기화 데이터 세그먼트에는 프로그램의 전역 초기화 변수가 포함됩니다.

  3. 초기화되지 않은 데이터 세그먼트 - 초기화되지 않은 데이터 세그먼트에는 프로그램의 초기화되지 않은 전역 변수가 포함됩니다. 이 섹션은 bss(더 나은 공간 절약)라고도 합니다.

    때로는 4000바이트가 필요한 int x[1000]과 같이 초기화되지 않은 큰 배열을 선언합니다. 그러나 이 공간은 어레이가 초기화될 때까지 필요하지 않습니다.

    따라서 이 공간은 예약되어 있지 않습니다. 포인터만 저장됩니다. 메모리는 배열이 초기화될 때 할당됩니다.

  4. 힙 - 힙 섹션에는 코딩 시 프로그래머가 정확한 크기를 알 수 없기 때문에 런타임 시 메모리가 요청되는 데이터가 포함됩니다.

  5. 스택 - 스택 섹션에는 모든 로컬 변수가 포함됩니다. 함수가 호출될 때마다 로컬 변수가 스택으로 푸시되고(따라서 스택이 커짐) 함수가 반환되면 로컬 변수가 팝됩니다(스택이 축소됨).

프로그램 세그먼트를 시각화하고 더 잘 이해하려면 다음 코드를 참조하세요.

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

이 프로그램에서 x는 전역적으로 초기화된 데이터이고 y는 전역적으로 초기화되지 않은 데이터입니다. 다음으로 숫자는 지역 변수입니다. 스택 영역으로 이동합니다.

x는 포인터, 다시 로컬 변수이며 스택 영역으로 이동합니다. new int[5]는 힙 영역에 공간을 할당합니다.

Unix 운영 체제 제품군에서 이 프로그램의 세그먼트를 쉽게 볼 수 있습니다.

C의 프로그램 세그먼트

C의 동적 메모리

많은 프로그램에서 프로그래머는 정확한 메모리 요구 사항을 모릅니다. 이러한 경우 프로그래머는 데이터 크기를 얻기 위해 사용자 또는 파일에서 입력을 받고 입력에 따라 런타임에 메모리를 선언합니다.

예를 참조하십시오.

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

이 프로그램에서 사용자는 크기를 입력하고 프로그램은 런타임에 크기에 따라 메모리를 할당합니다.

다중 프로그래밍 환경에서 운영 체제는 메모리 보호를 제공해야 합니다. 이는 프로그램이 자신의 의지 없이 데이터를 공유하지 못하도록 제한하는 것입니다.

따라서 모든 운영 체제는 프로그램이 잘못된 메모리에 액세스하지 못하도록 하는 메커니즘을 유지합니다.

프로그램을 위해 예약된 메모리에만 액세스할 수 있습니다. 프로그램의 주소 공간 외부에 있는 주소에 액세스하려고 시도하거나 프로그램의 할당된 메모리가 동적 할당 요청을 이행하기에 충분하지 않은 경우 세그먼트 오류가 발생할 수 있습니다.

이에 대해 자세히 논의합시다.

C의 분할 오류

프로그램이 도달할 수 없는 메모리 위치에 액세스하려고 하거나 메모리에 액세스할 수 있는 권한이 없는 경우 세그먼트 오류가 발생합니다. 아래에서 몇 가지 사례에 대해 논의해 보겠습니다.

초기화되지 않은 포인터를 참조하려고 합니다.

일부 컴파일러는 경우에 따라 경고를 표시하고 이 오류를 피하도록 도와주는 반면 다른 컴파일러는 그렇지 않기 때문에 이 오류는 혼란스러울 수 있습니다. 다음은 혼란스러운 흥미로운 예입니다.

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

위의 코드에서 우리는 할당되지 않은 포인터를 역참조하려고 합니다. 즉, 액세스 권한이 없는 메모리에 액세스하려고 합니다.

이 프로그램을 (cygwin) GCC 버전 9.3.0으로 컴파일하면 세그먼테이션 오류(코어 덤프됨)가 발생합니다.

g++ 9.3.0으로 컴파일하면 0이 출력됩니다.

이제 이 프로그램을 약간 변경하고 기능을 추가하면 다음과 같습니다.

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

이제 둘 다 인쇄 가비지 값을 출력으로 컴파일합니다. 할당되지 않은 메모리에 계속 액세스하려고 하기 때문에 혼란스럽습니다.

온라인 컴파일러에서 이것을 시도하면 유사한 동작이 나타납니다. 분할 오류가 비정상입니다. 경우에 따라 약간만 변경하면 이 오류가 추가되거나 제거될 수 있습니다.

이러한 종류의 오류를 방지하려면 포인터를 초기화하고 역참조하기 전에 포인터가 null이 아닌지 확인하십시오.

대용량 메모리 할당 시도

이 오류는 두 가지 방식으로 올 수 있습니다. 하나는 스택에 큰 크기의 배열을 선언할 때이고 두 번째는 힙에 큰 메모리를 선언할 때입니다.

우리는 둘 다 하나씩 볼 것입니다.

#include <stdio.h>

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

0의 수를 줄이면 출력은 Ok가 됩니다. 그러나 0을 계속 늘리면 어느 시점에서 프로그램이 중단되고 다음과 같이 표시됩니다.

Segmentation fault

그 이유는 스택 영역이 유한하기 때문입니다. 이는 이 대형 어레이에 필요한 메모리를 사용할 수 없음을 의미합니다.

결국 프로그램이 세그먼트를 벗어나려고 합니다.

더 많은 메모리(스택에서 사용 가능한 것보다 큰)가 필요한 경우 힙을 사용할 수 있습니다. 그러나 힙에도 한계가 있습니다. 따라서 메모리 크기를 계속 늘리면 오류가 발생합니다.

아래 예를 참조하십시오.

#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;
}

출력:

memory allocation is successful

그러나 크기를 계속 늘리면 한계를 초과하게 됩니다. 예를 들어 다음 크기에 문제가 있습니다.

long int size = 1000000000000000;  // 100000000000

위의 문장에서 더 많은 0을 셀 수 있습니다. 이 경우 프로그램이 충돌할 수 있습니다. 그러나 세그멘테이션 오류를 방지하기 위해 포인터가 NULL인지 여부를 확인했습니다.

‘NULL’은 요청한 공간을 사용할 수 없기 때문에 메모리가 할당되지 않았음을 의미합니다.

출력:

Space is not enough.

확인하지 않고 동적으로 할당된 메모리를 사용하여 이 코드를 시도하면 분할 오류가 발생합니다.

무한 루프 또는 재귀 호출

프로그램에 실수로 무한 루프를 남겨두면 특히 루프 내부에 동적 메모리를 할당하는 경우 분할 오류가 발생합니다.

동적 메모리 할당이 있는 무한 루프의 예가 제공됩니다.

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

여기에서 while (true)을 사용한 무한 루프를 볼 수 있습니다. 루프 내부의 메모리 할당문은 메모리를 해제하기 위해 free 메서드를 호출하지 않고 메모리 할당을 반복하기 때문에 결국 오류가 발생합니다.

마찬가지로 기본 사례를 추가하지 않고 재귀 함수를 만들면 스택이 오버플로될 수 있습니다. 아래 예를 참조하십시오.

void check() { check(); }

int main() { check(); }

위의 코드에서 check 함수는 계속 자신을 호출하고 프로그램에 사용 가능한 메모리가 소모되면 분할 오류를 일으키는 스택에 복사본을 생성합니다.

결론

프로그램이 도달할 수 없거나 사용할 수 없는 메모리에 액세스하려고 할 때 세그먼트 오류가 발생합니다. 역참조하기 전에 포인터가 메모리를 가리키는지 확인하십시오.

큰 공간이 필요한 경우 동적 메모리를 사용하고 포인터가 NULL인지 확인하십시오. scanf의 변수 앞에 &를 사용하고 printf% 뒤에 올바른 지정자를 사용해야 합니다.

크기를 벗어난 배열의 값을 할당하거나 액세스하려고 시도하지 마십시오. 선언할 때마다 항상 변수와 포인터를 초기화하십시오.

관련 문장 - C Error