programing

C에서 포인터 유형 간의 캐스팅이 정의되지 않은 동작은 언제입니까?

topblog 2023. 6. 22. 21:24
반응형

C에서 포인터 유형 간의 캐스팅이 정의되지 않은 동작은 언제입니까?

C의 신입으로서, 저는 포인터를 던지는 것이 실제로 언제가 괜찮은지 혼란스럽습니다.

제가 알기로는, 당신은 거의 모든 포인터 타입을 다른 타입으로 캐스팅할 수 있고, 컴파일러는 당신이 그것을 할 수 있도록 해줄 것입니다.예:

int a = 5;
int* intPtr = &a;
char* charPtr = (char*) intPtr; 

그러나 일반적으로 정의되지 않은 동작이 발생합니다(많은 플랫폼에서 작동하기는 하지만).그러나 다음과 같은 예외가 있습니다.

  • 당신은 여기저기 캐스팅할 수 있습니다.void*
  • 당신은 여기저기 캐스팅할 수 있습니다.char*

(적어도 코드에서 본 적은 있어요...)

그렇다면 C에서 정의되지 않은 동작이 포인터 유형 사이에 있는 캐스트는 무엇입니까?

편집:

저는 C 표준(http://c0x.coding-guidelines.com/6.3.2.3.html 의 "6.3.2.3 포인터" 섹션)을 조사하려고 했지만, 에 대한 부분을 제외하고는 실제로 이해하지 못했습니다.void*.

편집 2:

단지 설명을 위해:저는 명시적으로 "정상" 포인터에 대해서만 묻고 있습니다. 즉, 함수 포인터에 대해서는 묻지 않습니다.저는 함수 포인터를 캐스팅하는 규칙이 매우 제한적이라는 것을 알고 있습니다.사실, 저는 이미 그것에 대해 질문했습니다 :-:함수 포인터를 캐스팅하여 매개 변수 수를 변경하면 어떻게 됩니까?

기본적으로:

  • a T *롭게 자롭게변수있다니습할환유로 할 수 .void *서 그고다어시서 (디리어)서▁(다시▁and디▁again)T *는 함수 포인터가 아닙니다). 그러면 원래 포인터를 얻을 수 있습니다.
  • a T *롭게 자롭게변수있다니습할환유로 할 수 .U *서 그고다어시서 (디리어)서▁(다시▁and디▁again)T *그리고.U *는 함수 포인터가 아니며, 정렬 요구 사항이 동일한 경우 원래 포인터를 얻을 수 있습니다.그렇지 않으면 동작이 정의되지 않습니다.
  • 함수-스캐너는 다른 함수-스캐너 유형으로 자유롭게 변환된 후 다시 되돌릴 수 있으며, 그러면 원래 포인터를 얻을 수 있습니다.

참조: T *의 경우 ("-함수-함수-함수-함수-함수-함수-함수-함수-함수-함수-함수-함수-함수-함수-함수의 경우)의 시킵니다.char *.

중요:이 규칙들 중 어떤 것도 당신이 변환하면 무슨 일이 일어나는지에 대해 말하지 않습니다.T *U *그런 다음 언급을 취소하려고 합니다.그것은 기준의 전혀 다른 영역입니다.

Oli Charlesworth의 훌륭한 답변은 다른 유형의 포인터에 포인터를 던지는 것이 잘 정의된 결과를 제공하는 모든 경우를 나열합니다.

또한 포인터를 캐스팅하면 구현이 정의된 결과를 얻을 수 있는 네 가지 경우가 있습니다.

  • 충분히 큰(!) 정수 유형에 포인터를 캐스팅할 수 있습니다.에는 선택적인 C99가 .intptr_t그리고.uintptr_t정의 입니다.결과적으로 구현이 정의됩니다.메모리를 바이트의 연속 스트림("선형 메모리 모델", 대부분의 현대 플랫폼에서 사용됨)으로 주소를 지정하는 플랫폼에서, 일반적으로 포인터가 가리키는 메모리 주소의 숫자 값을 반환합니다. 따라서 단순히 바이트 카운트입니다.그러나 모든 플랫폼이 선형 메모리 모델을 사용하는 것은 아니므로 구현 정의:-)입니다.
  • 반대로 포인터에 정수를 캐스팅할 수 있습니다.가 정의유다대음큰경충우해분에 할 만큼 큰 있는 intptr_t또는uintptr_t포인터를 캐스팅하여 만든 것으로, 동일한 포인터 유형으로 다시 캐스팅하면 해당 포인터가 반환됩니다(그러나 더 이상 유효하지 않을 수 있음).그렇지 않으면 결과가 구현 정의됩니다.포인터를 실제로 참조 해제하는 것은 여전히 UB일 수 있습니다(단순히 값을 읽는 것과는 대조적).
  • 모든 개체에 포인터를 캐스팅할 수 있습니다.char*그런 다음 결과는 개체의 주소가 가장 낮은 바이트를 가리키며 포인터를 개체 크기까지 늘려 개체의 나머지 바이트를 읽을 수 있습니다.물론 실제로 얻을 수 있는 가치는 다시 구현으로 정의됩니다.
  • 널 포인터를 자유롭게 캐스팅할 수 있습니다. 포인터 유형에 관계없이 늘 널 포인터를 유지합니다. :-).

출처: C99 표준, 섹션 6.3.2.3 "포인터" 및 7.18.1.4 "오브젝트 포인터를 고정할 수 있는 정수 유형".

제가 알기로는 다른 유형의 포인터에 대한 포인터의 다른 모든 캐스트는 정의되지 않은 동작입니다.특히, 만약 당신이 캐스팅을 하지 않는다면.char또는 충분히 큰 정수 유형으로, 포인터를 다른 포인터 유형으로 캐스팅하는 것은 항상 UB일 수 있습니다. 참조를 다시 참조하지 않아도 됩니다.

이는 유형이 서로 다른 정렬을 가질 수 있고 서로 다른 유형이 호환되는 정렬을 수행할 수 있는 일반적인 휴대용 방법이 없기 때문입니다(서명/부호 정수 유형 쌍과 같은 일부 특수한 경우 제외).

일반적으로 요즘처럼 포인터 자체가 동일한 정렬 속성을 가지고 있다면 문제는 캐스트 자체가 아니라 포인터를 통해 데이터에 액세스할 수 있는지 여부입니다.

모든 유형 주조T*로.void*모든 개체 유형에 대해 뒤로 이동이 보장됩니다.T이렇게 하면 정확하게 동일한 포인터를 돌려받을 수 있습니다. void*catch all 객체 포인터 유형입니다.

개체 유형 사이의 다른 캐스트의 경우, 이러한 포인터를 통해 개체에 액세스하면 정렬(버스 오류), 정수의 트랩 표현과 같은 모든 종류의 문제가 발생할 수 있습니다.서로 다른 포인터 유형은 동일한 너비를 보장하지 않으므로 이론적으로 정보가 손실될 수도 있습니다.

하지만 항상 효과가 있어야 하는 한 가지 캐스팅은(unsigned char*)그런 다음 이러한 포인터를 통해 개체의 개별 바이트를 조사할 수 있습니다.

이 기준서의 작성자들은 다음과 같은 이유로 그러한 지원이 비쌀 수 있는 플랫폼에서 대부분의 포인터 유형 조합 간에 전환을 지원하는 원가와 효익을 저울질하려고 시도하지 않았습니다.

  1. 이러한 전환 비용이 많이 드는 대부분의 플랫폼은 이 기준서의 작성자들이 알지 못했던 플랫폼일 가능성이 높습니다.

  2. 이러한 플랫폼을 사용하는 사람들은 이러한 지원의 비용과 이점을 이 기준서의 작성자들보다 더 나은 위치에 놓이게 될 것입니다.

특정 플랫폼이 다음에 대해 다른 표현을 사용하는 경우int*그리고.double*나는 이 기준서가 의도적으로 예를 들어 라운드 드롭 변환의 가능성을 허용할 것이라고 생각합니다.double*로.int*그리고 다시.double*지속적으로 작동하지만 변환은int*로.double*그리고 다시.int*실패할 수 있습니다.

저는 이 기준서의 작성자들이 그러한 전환 비용이 전혀 들지 않는 플랫폼에서 그러한 작업이 실패할 수도 있다고 생각하지 않습니다.그들은 헌장과 근거 문서에서 C의 정신을 "프로그래머가 해야 할 일을 하는 것을 방해하거나 불필요하게 방해하지 말라"는 원칙을 포함하고 있다고 설명했습니다.그 원칙을 고려할 때, C의 정신을 유지하기 위해 성실한 노력을 하는 구현은 권한이 있든 없든 그러한 방식으로 행동하기 때문에, 구현이 프로그래머가 비용이 들지 않는 경우에 그들이 해야 할 일을 달성하는 데 도움이 되는 방식으로 작업을 처리하도록 의무화할 필요가 없습니다.

언급URL : https://stackoverflow.com/questions/4810417/when-is-casting-between-pointer-types-not-undefined-behavior-in-c

반응형