UFO ET IT

세분화 오류의 일반적인 원인에 대한 명확한 목록

ufoet 2021. 1. 8. 20:56
반응형

세분화 오류의 일반적인 원인에 대한 명확한 목록


참고 : 우리는 대체로 동일한 답변을 가진 많은 segfault 질문을 가지고 있으므로 정의되지 않은 참조에 대한 것과 같은 표준 질문으로 축소하려고합니다 .

우리가 다루는 문제가 있지만 세그먼트 오류가 무엇인지를 ,이 커버 무엇 여러 가지 이유를 나열하지 않습니다,하지만. 상위 답변은 "많은 이유가 있습니다"라고 말하고 하나만 나열하고 다른 답변의 대부분은 이유를 나열하지 않습니다.

대체로, 저는 이 주제에 대해 잘 조직 된 커뮤니티 위키 가 필요하다고 믿습니다 . 여기에는 세그 폴트를 얻는 모든 일반적인 원인 (그리고 일부)이 나열되어 있습니다. 목적은 답변의 면책 조항에서 언급했듯이 디버깅을 지원하는 것입니다.

나는 세그멘테이션 결함이 무엇인지 알고 있지만, 종종 어떻게 생겼는지 모르고 코드에서 발견하기 어려울 수 있습니다. 의심 할 여지없이 전체 목록을 나열하기에는 너무 많지만 C 및 C ++에서 세분화 오류의 가장 일반적인 원인은 무엇입니까?


경고!

다음은 세분화 오류의 잠재적 인 이유입니다. 모든 이유를 나열하는 것은 사실상 불가능합니다 . 이 목록의 목적은 기존 segfault를 진단하는 데 도움이됩니다.

분할 오류와 정의되지 않은 동작 사이의 관계는 충분히 강조 할 수 없습니다 ! 세분화 오류를 생성 할 수있는 아래의 모든 상황은 기술적으로 정의되지 않은 동작입니다. 이것은 그들이 segfault뿐만 아니라 무엇이든 할 수 있다는 것을 의미합니다. 누군가가 한때 USENET에서 말했듯이 " 컴파일러가 당신의 코에서 악마를 날아 오르게 하는 것은 합법적입니다. " 정의되지 않은 동작이있을 때마다 발생하는 segfault를 믿지 마십시오. C 및 / 또는 C ++에 정의되지 않은 동작이 어떤 것인지 배우고이를 포함하는 코드 작성을 피해야합니다!

정의되지 않은 동작에 대한 추가 정보 :


Segfault는 무엇입니까?

요컨대, 코드가 액세스 권한이없는 메모리에 액세스하려고 할 때 세분화 오류가 발생 합니다 . 모든 프로그램에는 작업 할 메모리 (RAM)가 제공되며 보안상의 이유로 해당 청크의 메모리에만 액세스 할 수 있습니다.

세그먼트 오류가 무엇인지에 대한보다 철저한 기술적 인 설명은 입니다 참조 세그먼트 오류 무엇입니까? .

다음은 세분화 오류 오류의 가장 일반적인 이유입니다. 다시 말하지만, 기존 segfault 진단에 사용되어야합니다 . 이를 피하는 방법을 배우려면 언어의 정의되지 않은 행동을 배우십시오 .

이 목록은 또한 자체 디버깅 작업을 수행하는 대신 사용할 수 없습니다 . (답변 하단의 해당 섹션을 참조하십시오.) 이것들은 찾을 수 있지만 디버깅 도구는 문제를 해결하는 유일한 신뢰할 수있는 방법입니다.


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

포인터가 NULL ( ptr=0)이거나 완전히 초기화되지 않은 경우 (아직 아무것도 설정되지 않음) 해당 포인터를 사용하여 액세스하거나 수정하려고하면 동작이 정의되지 않습니다.

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

실패한 할당 (예 : with malloc또는 new)은 null 포인터를 반환하므로 작업하기 전에 항상 포인터가 NULL이 아닌지 확인해야합니다.

또한 초기화되지 않은 포인터 (및 일반적으로 변수)의 값을 역 참조하지 않고 읽는 것도 정의되지 않은 동작입니다.

때때로 정의되지 않은 포인터에 대한 이러한 액세스는 이러한 포인터를 C print 문에서 문자열로 해석하려는 경우와 같이 매우 미묘 할 수 있습니다.

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.

일반적으로 리터럴 메모리 위치에 대한 포인터를 만들지 마십시오. 한 번 일하더라도 다음에 일하지 않을 수도 있습니다. 주어진 실행에서 프로그램의 메모리가 어디에 있는지 예측할 수 없습니다.

또한보십시오:


어레이의 끝을 지나서 읽으려고합니다.

An array is a contiguous region of memory, where each successive element is located at the next address in memory. However, most arrays don't have an innate sense of how large they are, or what the last element is. Thus, it is easy to blow past the end of the array and never know it, especially if you're using pointer arithmetic.

If you read past the end of the array, you may wind up going into memory that is uninitialized or belongs to something else. This is technically undefined behavior. A segfault is just one of those many potential undefined behaviors. [Frankly, if you get a segfault here, you're lucky. Others are harder to diagnose.]

// 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++;
}

Or the frequently seen one using for with <= instead of < (reads 1 byte too much):

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

Or even an unlucky typo which compiles fine (seen here) and allocates only 1 element initialized with dim instead of dim elements.

int* my_array = new int(dim);

Additionally it should be noted that you are not even allowed to create (not to mention dereferencing) a pointer which points outside the array (you can create such pointer only if it points to an element within the array, or one past the end). Otherwise, you are triggering undefined behaviour.

See also:


Forgetting a NUL terminator on a C string.

C strings are, themselves, arrays with some additional behaviors. They must be null terminated, meaning they have an \0 at the end, to be reliably used as strings. This is done automatically in some cases, and not in others.

If this is forgotten, some functions that handle C strings never know when to stop, and you can get the same problems as with reading past the end of an array.

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

With C-strings, it really is hit-and-miss whether \0 will make any difference. You should assume it will to avoid undefined behavior: so better write char str[4] = {'f', 'o', 'o', '\0'};


Attempting to modify a string literal

If you assign a string literal to a char*, it cannot be modified. For example...

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

...triggers undefined behavior, and a segmentation fault is one possible outcome.

See also:


Mismatching Allocation and Deallocation methods

You must use malloc and free together, new and delete together, and new[] and delete[] together. If you mix 'em up, you can get segfaults and other weird behavior.

See also:


Errors in the toolchain.

A bug in the machine code backend of a compiler is quite capable of turning valid code into an executable that segfaults. A bug in the linker can definitely do this too.

Particularly scary in that this is not UB invoked by your own code.

That said, you should always assume the problem is you until proven otherwise.


Other Causes

The possible causes of Segmentation Faults are about as numerous as the number of undefined behaviors, and there are far too many for even the standard documentation to list.

A few less common causes to check:


DEBUGGING

Debugging tools are instrumental in diagnosing the causes of a segfault. Compile your program with the debugging flag (-g), and then run it with your debugger to find where the segfault is likely occurring.

Recent compilers support building with -fsanitize=address, which typically results in program that run about 2x slower but can detect address errors more accurately. However, other errors (such as reading from uninitialized memory or leaking non-memory resources such as file descriptors) are not supported by this method, and it is impossible to use many debugging tools and ASan at the same time.

Some Memory Debuggers

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

Additionally it is recommended to use static analysis tools to detect undefined behaviour - but again, they are a tool merely to help you find undefined behaviour, and they don't guarantee to find all occurrences of undefined behaviour.

If you are really unlucky however, using a debugger (or, more rarely, just recompiling with debug information) may influence the program's code and memory sufficiently that the segfault no longer occurs, a phenomenon known as a heisenbug.

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

반응형