programing

MISRAC가 포인터의 복사본이 메모리 예외를 유발할 수 있다고 명시하는 이유는 무엇입니까?

megabox 2023. 9. 21. 20:13
반응형

MISRAC가 포인터의 복사본이 메모리 예외를 유발할 수 있다고 명시하는 이유는 무엇입니까?

MISRAC 2012 지침 4.12는 "동적 메모리 할당을 사용해서는 안 됩니다.

예를 들어, 문서는 다음과 같은 코드 샘플을 제공합니다.

char *p = (char *) malloc(10);
char *q;

free(p);
q = p; /* Undefined behaviour - value of p is indeterminate */

그리고 문서에는 다음과 같은 내용이 적혀 있습니다.

포인터에 저장된 값은 호출 후 free로 변경되지 않지만 일부 대상에서는 포인터가 가리키는 메모리가 더 이상 존재하지 않고 해당 포인터를 복사하는 행위인해 메모리 예외가 발생할 수 있습니다.

저는 거의 모든 문장은 끝만 빼고 괜찮아요.p와 q가 모두 스택에 할당되어 있는데, 포인터의 복사본이 어떻게 메모리 예외를 야기할 수 있습니까?

것은q = p;입니다.

J.2 정의되지 않은 동작 상태 읽기:

수명이 끝난 개체에 대한 포인터 값을 사용합니다(6.2.4).

이 장으로 들어가 보면 다음과 같습니다.

6.2.4 물건의 보관기간

개체의 수명(Lifetime)은 저장소가 개체를 위해 예약되는 동안 프로그램 실행의 일부입니다.객체가 존재하고, 일정한 주소(33)를 가지며, 마지막으로 저장된 값을 평생 유지합니다.34) 만약 어떤 대상이 수명 밖에 언급된다면, 그 행동은 정의되지 않습니다.포인터가 가리키는 개체(또는 과거)가 수명의 끝에 도달하면 포인터의 값이 결정되지 않습니다.

불확실한 것:

3.19.2 불확정 값: 불특정 값 또는 트랩 표현

포인터를 통해 개체를 자유롭게 하면 해당 메모리에 대한 모든 포인터가 불확정 상태가 됩니다. 불확정된 메모리를 읽는 것은 UB(Unfined behavior)입니다.다음은 UB:

char *p = malloc(5);
free(p);
if(p == NULL) // UB: even just reading value of p as here, is UB
{

}

첫번째로, 약간의 역사는...

ISO/IEC JTC1/SC22/WG14가 처음 C Language를 공식화하기 시작했을 때 (현재 ISO/IEC 9899:2011을 만들기 위해) 문제가 있었습니다.

많은 컴파일러 공급업체들이 다른 방식으로 상황을 해석했습니다.

그들은 초기에 기존의 기능을 파괴하지 않기로 결정했습니다.그래서 컴파일러 구현이 서로 다른 곳에서, 표준은 제공합니다.unspecified그리고.undefined

MISRAC는 이러한 행동이 유발할 함정을 함정에 빠뜨리려고 시도합니다.이론은 여기까지...

--

이제 이 질문의 구체적인 내용을 살펴보겠습니다.

free()의 목적이 동적 메모리를 다시 힙으로 방출하는 것이기 때문에, 모두 "야생에서" 구현된 세 가지가 가능했습니다.

  1. 포인터를 NULL로 재설정합니다.
  2. 포인터를 그대로 두다
  3. 포인터를 파괴합니다.

표준은 이 중할 수 으로 , 로 undefined 수 , 다른 컴파일러가 을 할 도 있습니다 -만,다...가정할 수 없고, 방법에 의존하는 것은 위험합니다.

개인적으로, 저는 표준이 구체적이고 포인터를 NULL로 설정하려면 자유()가 필요하지만, 그것은 제 의견일 뿐입니다.

--

그래서 TL;DR; 불행하게도, 대답은: 그것이 사실이기 때문입니다!

.p그리고.q둘 다이며,입니다.malloc()스택에 없습니다.

일단 성공적으로 할당된 메모리 영역이 해제되면, 그 시점에서 누가 메모리 영역을 사용하고 있는지 또는 메모리 영역의 배치를 알 수 없습니다.

그래서 한 번은free()는됩니다를 롭게 하는 데 됩니다.malloc()메모리 영역을 사용하려는 시도는 정의되지 않은 작업 유형입니다.운이 좋을 수도 있고 잘 될 겁니다.당신은 운이 없을 수도 있고 그렇지 않을 수도 있습니다.는.free()기억 영역은 당신이 더 이상 그것을 소유하지 않고 다른 것을 소유합니다.

여기서 문제는 어떤 컴퓨터 코드가 한 메모리 위치에서 다른 메모리 위치로 값을 복사할 때 관련된 것으로 보입니다.MISRA는 임베디드 소프트웨어 개발을 목표로 하기 때문에 항상 문제는 복사본을 가지고 어떤 특수한 프로세서를 사용하느냐 하는 것입니다.

MISRA 표준은 견고성, 신뢰성 및 소프트웨어 장애 위험 제거에 관한 것입니다.그들은 꽤 까다롭습니다.

의 입니다.p가리킨 메모리가 해제된 후에는 사용할 수 없습니다.일반적으로 초기화되지 않은 포인터의 값은 동일한 상태를 갖는데, 복사 목적으로 읽는 것만으로도 정의되지 않은 동작을 불러옵니다.

이 놀라운 제한의 이유는 함정 표현의 가능성 때문입니다.를 비우기p값이 트랩 표현이 될 수 있습니다.

나는 1990년대 초에 이런 식으로 행동했던 그러한 목표 중 하나를 기억합니다.그때는 내장된 대상이 아니라 그때는 널리 사용되고 있었습니다. 바로 Windows 2.x입니다.16비트 보호 모드에서 인텔 아키텍처를 사용했는데, 포인터는 32비트 너비였고, 16비트 선택기와 16비트 오프셋이 있었습니다.메모리에 액세스하기 위해 포인터는 특정 명령어와 함께 한 쌍의 레지스터(세그먼트 레지스터 및 주소 레지스터)에 로드되었습니다.

    LES  BX,[BP+4]   ; load pointer into ES:BX

포인터 값의 선택기 부분을 세그먼트 레지스터에 로드하면 선택기 값의 유효성을 검사하는 부작용이 발생했습니다. 선택기가 유효한 메모리 세그먼트를 가리키지 않으면 예외가 발생합니다.

하는 중입니다.q = p;다음과 같은 다양한 방법으로 컴파일할 수 있습니다.

    MOV  AX,[BP+4]    ; loading via DX:AX registers: no side effects
    MOV  DX,[BP+6]
    MOV  [BP-6],AX
    MOV  [BP-4],DX

아니면

    LES  BX,[BP+4]    ; loading via ES:BX registers: side effects
    MOV  [BP-6],BX
    MOV  [BP-4],ES

두 번째 옵션은 두 가지 이점이 있습니다.

  • 코드는 더 콤팩트하고 명령어는 1개 적습니다.

  • 포인터 값은 메모리를 참조 해제하는 데 직접 사용할 수 있는 레지스터에 로드되므로 이후 문장에 대해 생성되는 명령이 적어질 수 있습니다.

메모리를 비우면 세그먼트의 매핑이 해제되어 셀렉터가 비활성화될 수 있습니다.됩니다.ES:BX일부 아키텍처에 대한 트랩이라고도 불리는 예외를 발사합니다.

가 .LES단지 포인터 값을 복사하기 위한 명령은 더 느렸기 때문입니다. 그러나 몇몇은 콤팩트 코드를 생성하라는 명령을 받았을 때, 메모리가 다소 비싸고 부족했기 때문에 일반적인 선택이 되었습니다.

C 표준은 이를 허용하며 정의되지 않은 행동의 형태를 설명합니다. 코드는 다음과 같습니다.

수명이 끝난 개체에 대한 포인터 값을 사용합니다(6.2.4).

다음과 같이 정의된 대로 이 값이 결정되지 않았기 때문입니다.

3.19.2 불확정 값: 불특정 값 또는 트랩 표현

그러나 문자 유형을 통해 별칭을 지정하여 값을 조작할 수는 있습니다.

/* dumping the value of the free'd pointer */
unsigned char *pc = (unsigned char*)&p;
size_t i;
for (i = 0; i < sizeof(p); i++)
    printf("%02X", pc[i]);   /* no problem here */

/* copying the value of the free'd pointer */
memcpy(&q, &p, sizeof(p));   /* no problem either */

포인터를 해제한 후에 포인터를 검사하는 코드는 포인터가 다시 참조되지 않더라도 문제가 되는 두 가지 이유는 다음과 같습니다.

  1. C 표준의 저자들은 포인터들이 주변 메모리 블록들에 대한 정보를 포함하는 플랫폼들에서의 언어 구현들을 방해하고 싶지 않았고, 포인터들이 그것들을 제거하든 하지 않든 간에, 그러한 포인터들이 실행될 때마다 유효성을 확인할 수 있었습니다.이러한 플랫폼이 존재하는 경우 표준을 위반하여 포인터를 사용하는 코드는 이들과 함께 작동하지 않을 수 있습니다.

  2. 일부 컴파일러는 프로그램이 UB를 호출하는 입력의 조합을 수신하지 못할 것이며, 따라서 UB를 생성하는 입력의 조합은 불가능하다고 가정합니다.결과적으로 컴파일러가 이를 단순히 무시하면 대상 플랫폼에 나쁜 영향을 미치지 않는 UB 형태조차도 임의적이고 무제한적인 부작용을 초래할 수 있습니다.

IMHO, 자유 포인터에 대한 동등성, 관계형 또는 포인터 차이 연산자가 현대 시스템에 악영향을 미칠 이유는 없지만 컴파일러가 미친 "최적화"를 적용하는 것이 유행하기 때문에 일반적인 플랫폼에서 사용할 수 있어야 하는 유용한 구성이 위험해졌습니다.

샘플 코드의 어투가 좋지 않아서 기분이 언짢습니다.

"p의 값은 불확정"이라고 되어 있지만 p가 여전히 동일한 값(해제된 메모리 블록의 주소)을 가지고 있기 때문에 불확정된 p의 값은 아닙니다.

free(p)를 호출해도 p는 변경되지 않습니다. p는 p가 정의된 범위를 떠난 후에만 변경됩니다.

대신 메모리 블록이 해제되었기 때문에 p가 무엇을 가리키는지에 대한 값이며 운영 체제에서 매핑을 해제할 수도 있습니다.p 또는 별칭 포인터(q)를 통해 액세스하면 액세스 위반이 발생할 수 있습니다.

내면화해야 할 중요한 개념은 "불확정" 또는 "정의되지 않은" 행동의 의미입니다.그것은 바로 그것입니다: 알려지지 않은 것과 알 수 없는 것입니다.우리는 종종 학생들에게 "당신의 컴퓨터가 모양이 없는 덩어리로 녹거나 디스크가 화성으로 날아가는 것은 완전히 합법적입니다."라고 말하곤 합니다.원본 설명서를 읽어보니 malloc을 사용하지 말라는 곳이 보이지 않았습니다.그것은 단지 잘못된 프로그램이 실패할 것이라고 지적할 뿐입니다.사실, 프로그램이 메모리 예외를 두는 것은 프로그램에 결함이 있다는 것을 즉시 알려주기 때문에 좋은 일입니다.문서에서 이것이 나쁜 것일 수도 있다는 것을 암시하는 이유는 저를 피합니다.나쁜 점은 대부분의 아키텍처에서 메모리 예외가 발생하지 않는다는 것입니다.해당 포인터를 계속 사용하면 잘못된 값이 생성되어 힙을 사용할 수 없게 될 가능성이 있으며, 동일한 스토리지 블록이 다른 용도로 할당되면 해당 용도의 유효한 데이터가 손상되거나 해당 값이 자신의 값으로 해석됩니다.결론: 'stale' 포인터를 사용하지 마세요!다른 말로 하자면, 불량 코드를 쓴다는 것은 작동하지 않는다는 것을 의미합니다.

또한, p를 q에 할당하는 행위는 "정의되지 않음"이 가장 확실합니다.무의미한 넌센스인 변수 p에 저장된 비트들은 꽤 쉽고 정확하게 q에 복사됩니다. 지금 이 모든 것은 p에 의해 접근되는 모든 값은 이제 q에 의해 접근될 수 있고 p는 정의되지 않은 넌센스이므로 q는 이제 정의되지 않은 넌센스라는 것을 의미합니다.따라서 읽기 또는 쓰기에 둘 중 하나를 사용하면 "정의되지 않은" 결과를 얻을 수 있습니다.다행히도 아키텍처에서 실행 중이어서 메모리 오류가 발생할 수 있는 경우 부적절한 사용을 쉽게 감지할 수 있습니다.그렇지 않으면 두 포인터 중 하나를 사용하면 프로그램에 결함이 있음을 의미합니다.그것을 찾는데 많은 시간을 쓸 계획입니다.

언급URL : https://stackoverflow.com/questions/26704344/why-does-misra-c-state-that-a-copy-of-pointers-can-cause-a-memory-exception

반응형