programing

세그멘테이션 장애의 일반적인 이유의 최종 목록

copysource 2022. 7. 16. 13:32
반응형

세그멘테이션 장애의 일반적인 이유의 최종 목록

메모: segfault에 관한 질문은 많이 있습니다만, 답변은 거의 동일합니다.따라서 저는 그것들을 정의되지 않은 참조를 위해 표준적인 질문으로 정리하려고 합니다.

세그멘테이션 장애란 무엇인가에 대한 질문이 있습니다만, 그 이유는 별로 기재되어 있지 않습니다.맨 위의 답변에는 "여러 가지 이유가 있습니다"라고 적혀 있으며, 1개만 나열되며, 다른 대부분의 답변에는 어떠한 이유도 나열되지 않습니다.

대체로, 이 토픽에 관한 커뮤니티 Wiki가 잘 정리되어 있어야 한다고 생각합니다.이 토픽에는 세그먼트 폴트를 취득하는 모든 일반적인 원인(및 일부)이 기재되어 있습니다.그 목적은 답변의 면책사항에서 언급된 바와 같이 디버깅을 지원하는 것입니다.

세그멘테이션 장애는 알고 있지만, 코드 내에서 종종 어떻게 보이는지 모르면 찾기 어려울 수 있습니다.완전히 나열하기에는 너무 많은 것이 틀림없지만, C와 C++의 세그멘테이션 장애의 가장 일반적인 원인은 무엇입니까?

경고!

다음은 분할 장애의 잠재적 원인입니다.모든 이유를 나열하는 것은 사실상 불가능하다.이 목록의 목적은 기존 seg fault를 진단하는 데 도움이 됩니다.

분할 장애와 정의되지 않은 동작 간의 관계는 아무리 강조해도 지나치지 않습니다! 분할 장애를 생성할 수 있는 다음 상황은 모두 기술적으로 정의되지 않은 동작입니다.이것은, 유에스넷에서 「컴파일러가 당신의 코로부터 악마를 날려보내는 것은 합법이다」라고 말한 적이 있는 것처럼, 세그먼트 폴트 뿐만이 아니라, 그들이 무엇이든 할 수 있다는 것을 의미합니다.정의되지 않은 동작을 할 때마다 segfault가 발생할 것으로 기대하지 마십시오.C 및/또는 C++에 존재하는 정의되지 않은 동작을 학습하고 이러한 동작을 포함하는 코드를 쓰는 것을 피해야 합니다.

정의되지 않은 동작에 대한 자세한 정보:


세그먼트 폴트란

즉, 코드가 액세스 권한이 없는 메모리에 액세스하려고 할 때 세그멘테이션 장애가 발생합니다.모든 프로그램에는 사용할 수 있는 메모리(RAM)가 제공되며 보안상의 이유로 해당 청크의 메모리에만 액세스할 수 있습니다.

세그멘테이션 장애에 대한 자세한 기술적 설명은 세그멘테이션 장애란? 참조하십시오.

다음은 세그멘테이션 오류의 가장 일반적인 원인입니다.이 경우에도 기존 seg fault를 진단할 때 사용해야 합니다.그것들을 피하는 방법을 배우려면, 당신의 언어에서 정의되지 않은 행동을 배워라.

리스트는, 독자적인 디버깅 작업을 실시하기 위해서도 대체되지 않습니다(답변 하단의 항을 참조해 주세요).이러한 것들을 찾을 수 있지만, 디버깅툴이 유일하게 신뢰할 수 있는 방법으로 문제를 파악할 수 있습니다.


NULL 또는 초기화되지 않은 포인터 액세스

NULL인 포인터가 있는 경우(ptr=0또는 완전히 초기화되지 않은(아직 아무것도 설정되어 있지 않음) 포인터를 사용하여 액세스 또는 변경을 시도하려고 하면 동작이 정의되지 않았습니다.

int* ptr = 0;
*ptr += 5;

할당에 실패했기 때문에(예를 들어,malloc또는new)는 늘 포인터를 반환하므로 포인터를 사용하기 전에 항상 포인터가 NULL이 아닌지 확인해야 합니다.

또한 초기화되지 않은 포인터(및 일반적으로 변수)의 값을 (참조하지 않고) 읽는 도 정의되지 않은 동작이라는 점에 유의하십시오.

이러한 미정의 포인터의 액세스는, 예를 들면, C프린트 스테이트먼트의 문자열로서 그러한 포인터를 해석하려고 하는 등, 매우 미묘한 경우가 있습니다.

char* ptr;
sprintf(id, "%s", ptr);

다음 항목도 참조하십시오.


행잉 포인터 접근

사용하시는 경우malloc또는new메모리를 할당하고 나중에free또는delete포인터를 통해 그 포인터는 이제 매달리는 포인터로 간주됩니다.참조 해제(NULL과 같은 새 값을 할당하지 않은 경우)는 정의되지 않은 동작으로 세그먼트화 오류가 발생할 수 있습니다.

Something* ptr = new Something(123, 456);
delete ptr;
std::cout << ptr->foo << std::endl;

다음 항목도 참조하십시오.


스택 오버플로

[아니야, 지금 있는 사이트 말고 이름 붙여진 거]지나치게 단순화하면, "스택"은 주문서를 일부 고객에게 붙이는 스파이크와 같습니다.예를 들어 스파이크에 너무 많은 주문을 하면 이 문제가 발생할 수 있습니다.컴퓨터에서는 동적으로 할당되지 않은 변수와 CPU에 의해 아직 처리되지 않은 명령어는 스택으로 이동합니다.

이 문제의 원인 중 하나는 깊은 재귀 또는 무한 재귀일 수 있습니다(예를 들어 함수가 정지할 방법이 없는 경우).스택이 오버플로우 되어 있기 때문에 주문 용지가 "떨어지기" 시작하고, 주문 용지에 적합하지 않은 다른 공간을 차지하게 됩니다.따라서 분할 결함이 발생할 수 있습니다.또 다른 원인으로는 대규모 어레이를 초기화하려고 하는 경우가 있습니다.단일 주문에 불과하지만 이미 충분히 큰 어레이만 초기화하려고 합니다.

int stupidFunction(int n)
{
   return stupidFunction(n);
}

스택 오버플로의 또 다른 원인은 한꺼번에 너무 많은(동적으로 할당되지 않은) 변수가 있는 것입니다.

int stupidArray[600851475143];

야생에서 스택 오버플로의 한 가지 사례는 단순히 누락으로 인해 발생하였습니다.return함수의 무한 재귀를 방지하기 위한 조건문입니다.그 이야기의 교훈은 항상 오류 체크가 효과가 있는지 확인하는 것입니다.

다음 항목도 참조하십시오.


와일드 포인터

메모리 내의 임의의 장소에 포인터를 작성하는 것은 코드를 사용하여 러시안 룰렛을 플레이하는 것과 같습니다.접근 권한이 없는 장소에 포인터를 작성하거나 놓치기 쉽습니다.

int n = 123;
int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people.

일반적으로 리터럴 메모리 위치에 대한 포인터는 작성하지 마십시오.한 번 작동해도 다음 번에는 작동하지 않을 수 있습니다.실행 시 프로그램 메모리의 위치를 예측할 수 없습니다.

다음 항목도 참조하십시오.


어레이의 끝을 지나 읽으려고 시도하고 있습니다.

어레이는 메모리의 인접 영역이며, 연속되는 각 요소는 메모리의 다음 주소에 위치합니다.그러나 대부분의 어레이는 어레이의 크기나 마지막 요소의 크기에 대한 타고난 감각을 가지고 있지 않습니다.따라서 특히 포인터 계산을 사용하는 경우 배열의 끝을 지나쳐도 전혀 알 수 없습니다.

어레이의 끝을 지나 읽으면 초기화되지 않았거나 다른 것에 속하는 메모리가 될 수 있습니다.이것은 기술적으로 정의되지 않은 동작입니다.세그먼트 폴트는 정의되지 않은 많은 잠재적인 동작 중 하나일 뿐입니다.[솔직히 여기서 세그 폴트가 나오면 운이 좋은 거야]진단이 어려운 것도 있습니다.]

// like most UB, this code is a total crapshoot.
int arr[3] {5, 151, 478};
int i = 0;
while(arr[i] != 16)
{
   std::cout << arr[i] << std::endl;
   i++;
}

또는 자주 볼 수 있는 것은for와 함께<=대신<(1바이트 초과):

char arr[10];
for (int i = 0; i<=10; i++)
{
   std::cout << arr[i] << std::endl;
}

또는 (여기에 표시된) 미세한 컴파일 및 초기화된1개의 요소만 할당하는 불운한 오타도 있습니다.dim대신dim요소들.

int* my_array = new int(dim);

또, 어레이의 외부를 가리키는 포인터의 작성(디레퍼런스는 말할 것도 없고)도 허가되어 있지 않은 것에 주의해 주세요(이 포인터는, 어레이내의 요소 또는 그 끝의 요소를 가리키는 경우에만 작성할 수 있습니다).그렇지 않으면 정의되지 않은 동작이 트리거됩니다.

다음 항목도 참조하십시오.


C 문자열의 NUL 터미네이터를 잊어버렸다.

C 문자열은 그 자체로 몇 가지 추가 동작이 있는 어레이입니다.null로 종료해야 합니다.즉, null로 종료되는 것은\0문자열로 안정적으로 사용할 수 있습니다.이 작업은 자동으로 실행되는 경우도 있고 그렇지 않은 경우도 있습니다.

이것을 잊어버렸을 경우, C 문자열을 처리하는 일부 함수는 정지할 타이밍을 알 수 없으며, 어레이의 끝을 지나 읽었을 때와 같은 문제가 발생할 수 있습니다.

char str[3] = {'f', 'o', 'o'};
int i = 0;
while(str[i] != '\0')
{
   std::cout << str[i] << std::endl;
   i++;
}

C-스트링의 경우, 이것은 정말 히트 앤 미스입니다.\0어떤 변화도 가져올거야.정의되지 않은 동작을 회피하는 것으로 상정해야 합니다.그러므로 글을 쓰는 것이 좋습니다.char str[4] = {'f', 'o', 'o', '\0'};


문자열 리터럴 수정 시도 중

문자열 리터럴을 char*에 할당하면 변경할 수 없습니다.예를 들면...

char* foo = "Hello, world!"
foo[7] = 'W';

...정의되지 않은 동작을 방지하고 분할 장애를 생각할 수 있는 결과 중 하나의 결과입니다.

다음 항목도 참조하십시오.


할당 및 할당 해제 방법 불일치

를 사용해야 합니다.malloc그리고.free함께.new그리고.delete함께, 그리고new[]그리고.delete[]함께.그것들을 섞으면 세그먼트 폴트나 다른 이상한 행동을 할 수 있어요.

다음 항목도 참조하십시오.


툴 체인 오류입니다.

컴파일러의 머신 코드 백엔드에 있는 버그는 유효한 코드를 세그먼트 폴트를 일으키는 실행 파일로 바꿀 수 있습니다.링커에 있는 버그도 이 기능을 할 수 있습니다.

특히 무서운 것은 이것이 당신의 코드에 의해 호출된 UB가 아니라는 점입니다.

단, 그렇지 않다는 이 증명될 까지는 항상 문제가 자신이라고 가정해야 합니다.


기타 원인

Segmentation Fault의 가능한 원인은 정의되지 않은 동작의 수와 거의 비슷하며 표준 문서조차 나열할 수 없을 정도로 많습니다.

확인해야 할 몇 가지 일반적인 원인은 다음과 같습니다.


디버깅

우선 코드를 잘 읽어보세요.대부분의 오류는 단순히 오타나 실수로 인해 발생합니다.분할 결함의 모든 잠재적 원인을 확인하십시오.이 작업이 실패하면 전용 디버깅툴을 사용하여 근본적인 문제를 찾아내야 할 수 있습니다.

디버깅 도구는 세그먼트 장애의 원인을 진단하는 데 중요합니다.디버깅 플래그를 사용하여 프로그램을 컴파일합니다(-gsegfault가 발생할 가능성이 있는 장소를 특정하기 위해 디버거와 함께 실행합니다.

최신 컴파일러는 다음을 통한 구축을 지원합니다.-fsanitize=address이 경우 일반적으로 프로그램은 약 2배 느리게 실행되지만 주소 오류를 더 정확하게 탐지할 수 있습니다.단, 이 방법에서는 다른 오류(초기화되지 않은 메모리에서 읽거나 파일 기술자 등의 비메모리 리소스 누수 등)는 지원되지 않으며 다수의 디버깅툴과 ASAN을 동시에 사용할 수 없습니다.

일부 메모리 디버거

  • GDB | Mac, Linux
  • valgrind (memcheck)| Linux
  • Dr. Memory

또한 정의되지 않은 동작을 검출하기 위해 정적 분석 도구를 사용하는 것이 좋습니다.그러나 이는 단순히 정의되지 않은 동작을 찾는 데 도움이 되는 도구일 뿐이며 정의되지 않은 동작의 모든 발생을 찾는 것을 보장하는 것은 아닙니다.

그러나, 만약 당신이 정말로 운이 나쁘다면, 디버거를 사용하는 것(또는 더 드물게, 단지 디버그 정보로 재컴파일 하는 것)은 프로그램의 코드와 메모리에 영향을 미쳐 더 이상 segfault가 발생하지 않는 현상인 하이젠버그로 알려져 있다.

이 경우 코어 덤프를 가져와 디버거를 사용하여 역추적을 얻는 것이 좋습니다.

언급URL : https://stackoverflow.com/questions/33047452/definitive-list-of-common-reasons-for-segmentation-faults

반응형