[c] 왜 이것이 역 참조 타입 천공 포인터 경고 컴파일러에 해당한다고 주장합니까?

Stack Overflow RE 에 대한 다양한 게시물 읽었습니다 : derefercing type-punned pointer error. 내 이해는 오류가 본질적으로 다른 유형의 포인터를 통해 객체에 액세스 할 위험에 대한 컴파일러 경고라는 것입니다 (예외가 있지만 ) 이해할 수 있고 합리적인 경고입니다.char*

내 질문은 아래 코드와 관련이 있습니다. 포인터 주소를 캐스팅 void**하여이 경고를받을 수 있는 이유는 -Werror무엇입니까?

또한이 코드는 여러 대상 아키텍처 용으로 컴파일되며 그 중 하나만 경고 / 오류를 생성합니다. 이는 합법적으로 컴파일러 버전 별 결함임을 암시합니까?

// main.c
#include <stdlib.h>

typedef struct Foo
{
  int i;
} Foo;

void freeFunc( void** obj )
{
  if ( obj && * obj )
  {
    free( *obj );
    *obj = NULL;
  }
}

int main( int argc, char* argv[] )
{
  Foo* f = calloc( 1, sizeof( Foo ) );
  freeFunc( (void**)(&f) );

  return 0;
}

위에서 언급 한 내 이해가 정확 void**하고 여전히 포인터 인 경우 안전한 캐스팅이되어야합니다.

이 컴파일러 관련 경고 / 오류 를 완화 시키는 lvalue사용하지 않는 해결 방법 이 있습니까? 즉, 이것이 문제를 해결하는 이유를 이해하지만 의도 된 out-arg 를 freeFunc() NULL로 사용 하고 싶기 때문에이 접근법을 피하고 싶습니다 .

void* tmp = f;
freeFunc( &tmp );
f = NULL;

문제 컴파일러 (하나 중 하나) :

user@8d63f499ed92:/build$ /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc --version && /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-fc3-linux-gnu-gcc (GCC) 3.4.5
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./main.c: In function `main':
./main.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules

user@8d63f499ed92:/build$

불평하지 않는 컴파일러 (다수 중 하나) :

user@8d63f499ed92:/build$ /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc --version && /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-rh73-linux-gnu-gcc (GCC) 3.2.3
Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

user@8d63f499ed92:/build$

업데이트 : 추가로 컴파일 할 때 경고가 특별히 생성 된 것으로 나타났습니다 -O2(여전히 “문제가있는 컴파일러”만 있음)



답변

type 값은 type void**객체에 대한 포인터 void*입니다. 유형의 객체는 유형의 객체 Foo*가 아닙니다 void*.

사이의 암시 적 변환이 유형 Foo*void*. 이 변환은 값의 표현을 변경할 수 있습니다. 비슷하게, 당신은 쓸 수 int n = 3; double x = n;있고 이것은 x값으로 잘 정의 된 동작을 가지고 3.0있지만 double *p = (double*)&n;정의되지 않은 동작을 가지고 있습니다 (실제로 일반적인 아키텍처에서 p“포인터”로 설정되지 않습니다 3.0).

객체에 대한 다른 유형의 포인터가 다른 표현을 갖는 아키텍처는 오늘날 드물지만 C 표준에서는 허용됩니다. 메모리에있는 단어의 주소 인 단어 포인터 와이 단어의 바이트 오프셋과 함께 단어의 주소 인 바이트 포인터 를 가진 (희귀 한) 오래된 기계가 있습니다 . Foo*단어 포인터 void*가 될 것이고 그러한 아키텍처에서 바이트 포인터가 될 것입니다. 객체의 주소뿐만 아니라 유형, 크기 및 액세스 제어 목록에 대한 정보를 포함 하는 뚱뚱한 포인터 가있는 (희귀 한) 기계가 있습니다. 명확한 유형에 대한 포인터 void*는 런타임에 추가 유형 정보가 필요한 것과 다른 표현을 가질 수 있습니다 .

이러한 기계는 드물지만 C 표준에 의해 허용됩니다. 그리고 일부 C 컴파일러는 코드를 최적화하기 위해 유형 천공 포인터를 고유하게 취급 할 수있는 권한을 이용합니다. 포인터 앨리어싱의 위험은 컴파일러가 코드를 최적화하는 기능의 주요 제한 사항이므로 컴파일러는 이러한 권한을 이용하는 경향이 있습니다.

컴파일러는 자신이 잘못한 일을하고 있거나 원하지 않는 일을 조용히하거나 원하는 일을 조용히한다고 자유롭게 말할 수 있습니다. 정의되지 않은 동작은 이들 중 하나를 허용합니다.

freefunc매크로 를 만들 수 있습니다 :

#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)

여기에는 매크로의 일반적인 한계가 있습니다. 형식 안전성 부족, p두 번 평가됩니다. 이것은 p해제 된 객체에 대한 단일 포인터 인 경우 매달려있는 포인터를 떠나지 않는 안전을 제공 합니다.


답변

A void *는 불완전한 유형을 참조하기 때문에 C 표준에 의해 특별히 처리됩니다. 이 치료는 않습니다 하지 으로 확장 void **이로 하지 특히, 완전한 형태에 포인트를 void *.

엄격한 앨리어싱 규칙에 따르면 한 유형의 포인터를 다른 유형의 포인터로 변환 할 수 없으며 이후에 해당 포인터를 역 참조 할 수 없습니다. 그렇게하면 한 유형의 바이트를 다른 유형의 바이트로 해석 할 수 있기 때문입니다. 객체 유형을 읽을 수있는 문자 유형으로 변환하는 경우는 예외입니다.

함수 대신 함수와 유사한 매크로를 사용하여이 제한을 해결할 수 있습니다.

#define freeFunc(obj) (free(obj), (obj) = NULL)

다음과 같이 호출 할 수 있습니다 :

freeFunc(f);

그러나 위의 매크로가 obj두 번 평가되므로 제한이 없습니다 . GCC를 사용하는 경우 일부 확장, 특히 typeof키워드 및 명령문 표현식 에서이를 피할 수 있습니다 .

#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })


답변

유형 천공 포인터의 역 참조는 UB이며 어떤 일이 일어날 지 의지 할 수 없습니다.

다른 컴파일러는 다른 경고를 생성하며,이를 위해 동일한 컴파일러의 다른 버전을 다른 컴파일러로 간주 할 수 있습니다. 이것은 아키텍처에 의존하는 것보다 분산에 대한 더 나은 설명 인 것 같습니다.

이 경우 유형 punning이 나쁜 이유를 이해하는 데 도움이 될 수있는 경우는 함수가 아키텍처에서 작동하지 않는다는 것입니다 sizeof(Foo*) != sizeof(void*). 이것이 현재의 표준을 알지 못하더라도 표준에 의해 승인되었습니다.

해결 방법은 함수 대신 매크로를 사용하는 것입니다.

free널 포인터 를 허용합니다.


답변

이 코드는 C 표준에 따라 유효하지 않으므로 경우에 따라 작동 할 수 있지만 반드시 이식 가능하지는 않습니다.

다른 포인터 유형으로 캐스트 된 포인터를 통해 값에 액세스하기위한 “엄격한 앨리어싱 규칙”은 6.5 단락 7에 있습니다.

객체는 다음 유형 중 하나를 갖는 lvalue 표현식으로 만 저장된 값에 액세스해야합니다.

  • 객체의 유효 유형과 호환되는 유형

  • 유효 객체 유형과 호환되는 유형의 정규화 된 버전

  • 객체의 유효 유형에 해당하는 부호있는 유형 또는 부호없는 유형 인 유형

  • 오브젝트의 유효 유형의 규정 된 버전에 해당하는 부호있는 유형 또는 부호없는 유형 인 유형

  • 상기 구성원들 중 상기 유형들 중 하나를 포함하는 집합 또는 연합 유형 (재귀 적으로 하위 집합의 구성원 또는 포함 된 연합을 포함) 또는

  • 문자 유형

당신의에서 *obj = NULL;문, 객체는 효과적인 유형이 Foo*있지만, 좌변 식으로 액세스 할 *obj유형 void*.

6.7.5.1 단락 2에서

두 개의 포인터 유형이 호환 되려면 둘 다 동일하게 규정되어야하며 둘 다 호환 가능한 유형에 대한 포인터 여야합니다.

그래서 void*Foo*호환되는 유형 또는 추가 규정과 호환 유형이 아니며, 확실히 엄격한 앨리어싱 규칙의 다른 옵션을 적합하지 않습니다.

코드가 유효하지 않은 기술적 이유는 아니지만 섹션 6.2.5 단락 26과 관련이 있습니다.

에 대한 포인터 void는 문자 유형에 대한 포인터와 동일한 표현 및 정렬 요구 사항을 가져야합니다. 마찬가지로, 호환 가능 유형의 적격 또는 비 적격 버전에 대한 포인터는 동일한 표현 및 정렬 요구 사항을 가져야합니다. 구조 유형에 대한 모든 포인터는 서로 동일한 표현 및 정렬 요구 사항을 가져야합니다. 공용체 유형에 대한 모든 포인터는 서로 동일한 표현 및 정렬 요구 사항을 가져야합니다. 다른 유형에 대한 포인터는 동일한 표현 또는 정렬 요구 사항을 가질 필요는 없습니다.

경고의 차이점에 대해서는 표준에 진단 메시지가 필요한 경우가 아니므로 컴파일러 또는 해당 버전이 잠재적 인 문제를 파악하고 유용한 방법으로 지적하는 것이 얼마나 좋은가에 달려 있습니다. 최적화 설정에 차이가있을 수 있습니다. 다양한 프로그램이 실제로 실제로 어떻게 적용되는지에 대한 추가 정보가 내부적으로 생성되기 때문에 추가 정보가 경고 확인에 사용되기도합니다.


답변

다른 답변의 말과 더불어, 이것은 C 의 고전적인 안티 패턴 이며, 불로 태워야합니다. 다음과 같이 나타납니다.

  1. 경고를 찾은 것과 같은 자유 및 널 아웃 기능
  2. 반환의 표준 C 관용구를 피하는 할당 함수 void *( punning 유형 대신 값 변환 이 포함되어 있기 때문에이 문제가 발생하지 않음 ) 대신 오류 플래그를 반환하고 포인터 대 포인터를 통해 결과를 저장합니다.

(1)의 다른 예에서, ffmpeg / libavcodec의 av_free기능 에서 오랫동안 악명 높은 사례가있었습니다 . 나는 그것이 결국 매크로 또는 다른 트릭으로 수정되었다고 생각하지만 확실하지 않습니다.

(2)의 경우 cudaMallocposix_memalign모두 예입니다.

두 경우 모두 인터페이스가 본질적으로 유효하지 않은 사용을 요구 하지는 않지만 인터페이스를 강력하게 권장 void *하고 자유 및 널 아웃 기능의 목적을 무효화 하고 할당을 어색하게 만드는 추가 임시 유형의 오브젝트로만 올바른 사용법을 허용합니다 .


답변

C는 모든 포인터에 대해 동일한 표현을 사용하는 기계 용으로 설계되었지만 표준 작성자는 다른 유형의 객체에 대한 포인터에 다른 표현을 사용하는 기계에서 언어를 사용할 수 있기를 원했습니다. 따라서 많은 종류의 시스템에서 비용이 많이 들지 않지만 서로 다른 종류의 포인터에 대해 다른 포인터 표현을 사용하는 시스템이 “모든 포인터 유형에 대한 포인터”유형을 지원할 필요는 없습니다.

표준이 작성되기 전에, 모든 포인터 유형에 대해 동일한 표현을 사용하는 플랫폼의 구현 void**은 최소한 적합한 캐스팅을 사용하여 “포인터에 대한 포인터” 로 만장일치로 사용할 수 있습니다. 이 표준의 저자는 이것이 표준을 지원하는 플랫폼에서 유용 할 것이라는 점을 거의 확실하게 인식했지만 보편적으로 지원할 수 없었기 때문에 표준을 거부했습니다. 대신, 품질 구현은 합리적 일 경우 합리적 설명이 “인기 확장”으로 설명되는 것과 같은 구성을 처리 할 것으로 예상했습니다.


답변