2013-06-29 4 views
14

예를 들어,이 코드가 유효합니까 아니면 별칭 규칙을 위반하여 정의되지 않은 동작을 호출합니까?구조체 별칭은 초기 및 유일한 멤버가 될 수 있습니까?

int x; 
struct s { int i; } y; 
x = 1; 
y = *(struct s *)&x; 
printf("%d\n", y.i); 

별칭 읽기를 수행하기위한 이식 가능한 방법을 개발하기 위해이 기술을 사용하는 데 관심이 있습니다.

업데이트 : 여기에 의도 된 사용의 경우, 약간의 차이가 있지만 위의 유효한의 경우에만 유효합니다

static inline uint32_t read32(const unsigned char *p) 
{ 
    struct a { char r[4]; }; 
    union b { struct a r; uint32_t x; } tmp; 
    tmp.r = *(struct a *)p; 
    return tmp.x; 
} 

GCC, 원하는대로, 32 단일이 컴파일 비트로드가 발생하고 p이 실제로 char 이외의 유형을 가리키는 경우 발생할 수있는 별칭 문제를 피하는 것으로 보입니다. 즉, GNU C __attribute__((__may_alias__)) 속성을 대체 할 수있는 대체 기능으로 보인다. 하지만 C 표준에서

+7

에서 재 작업되었습니다. – OldProgrammer

+3

** 이것은 완벽하게 유효합니다. ** 표준의 정확한 부분/인용문을 기억하지 못하지만이를 수행 할 수 있습니다. 마치 첫 번째 구조체의 초기 멤버를 공유하는 구조체를 가리키는 포인터를 통해 구조체의 별칭을 지정할 수 있습니다. –

+0

@ H2CO3 그래서 패딩은 * 필드 사이에서만 발생할 수 있습니까? – Elazar

답변

-1

... 정말 잘 정의인지 불확실 해요 :

개체 또는 불완전한 유형에 대한 포인터가 다른 개체 또는 불완전에 대한 포인터로 변환 할 수있다 유형. pointed-type 유형에 대해 결과 포인터가 올바르게 정렬되지 않으면 (57) 동작이 정의되지 않습니다.

이 경우에 생성 된 포인터 (구조체의 제 1 부재는 구조체와 일치 있어야하기 때문에) 정확하게 정렬되도록 보장이기 때문에 이러한 제한은 여기에 적용되지 않는다. 적용되는 것은 객체에 대한 액세스가 객체의 "유효 유형"과 호환되는 포인터를 통해서만 필요하다는 포인터 사용에 대한 추가 제한입니다.이 경우 유효한 유형 인 xint이므로 다음을 통해 액세스 할 수 없습니다. struct 포인터.

일부 클레임과 달리 포인터 유형 간의 변환은 왕복 사용에만 국한되지 않습니다. 표준은 포인터가 변환 될 수 있다고 말합니다. 단, 이러한 변환으로 인해 정의되지 않은 동작이 발생하는 경우는 예외입니다. 다른 곳에서는 결과 유형의 포인터 사용에 대한 의미를 제공합니다. 표준의 왕복 보장추가 사양 ... 당신이 명시 적으로 언급하지 수없는 경우 당신이 믿을 수있는 것들 : 다시 변환 할 때 그렇지 않으면

을, 결과는 비교된다 원래 포인터와 같습니다.

이것은 왕복에 대한 보장을 지정하며 왕복 여행에 제한이 없습니다.

그러나 "유효 유형"언어 은 변환으로 인한 포인터 사용에 대한 제한 인입니다. 그것이 적절한 대답,하지만이 (두 번째 예에서) 일어날 수

+0

이 인용문에서는 포인터를 통한 값 액세스를 다루지 않고 성공한 변환 만 처리합니다. 왕복 여행용. –

+1

@R .. 아니요, 왕복 여행용이 아닙니다. 표준에 따르면 변환 될 수 있습니다. 그것은 결과 포인터의 사용에 더 이상의 제한을 두지 않습니다. "void에 대한 포인터는 불완전한 객체 또는 객체 유형의 포인터로 변환되거나 변환 될 수 있습니다."- 결과에 제한이 없으며 우리는 이것을 자유롭게 사용합니다. –

+0

@R .. 참고 : "한 유형의 함수에 대한 포인터가 또 다른 유형의 함수에 대한 포인터로 변환 될 수 있으며 그 결과는 원래 포인터와 동일하게 비교됩니다. 변환 된 포인터가 사용되면 형식이 가리키는 형식과 호환되지 않는 함수를 호출하려면 동작이 정의되지 않았습니다. " - 표준에서는 결과 포인터로 호출 할 수 있다고 명시 적으로 말하지 않으며 명시 적으로 정의되지 않은 사용은 해당 유형의 포인터에 대한 표준 의미에 의해 정의됩니다. –

0

확실하지 이것이다 :

  1. 컴파일러는 배열의 4 바이트 후 패딩, 8 바이트 개체로 struct a를 정의 (왜?).
  2. 그런 다음 struct a (즉 8 바이트 개체)의 주소로 p를 처리하는 tmp.r = *(struct a *)p;을 사용합니다. 이 개체의 내용을 tmp.r, 즉 p이 보유하는 주소에서 8 바이트로 복사하려고 시도합니다. 하지만 거기에서 4 바이트 만 읽을 수 있습니다.

구현은 패딩 바이트를 복사 할 필요는 없지만 그렇게 할 수 있습니다.

+0

이것은 좋은 이의입니다. 그러나 구조 유형의 패딩은 구현에 사용 된 ABI와 실제 ABI에서 결코 고려하지 않는 상황에 의해 잘 정의되어 있으므로이 가능성을 무시하는 데 만족합니다. BTW,이 문제가 적용되는지 여부는'sizeof'로 쉽게 테스트 할 수 있습니다. 나는 현실 세계에서 확실히 중요 할 수있는 앨리어싱 문제에 대해 훨씬 더 우려하고 있으며 원칙적으로 테스트 할 수없는 문제는 아닙니다. –

5

나는 여전히 효과적인 타이핑 규칙을 위반한다고 생각합니다. 명시 적으로 선언되지 않았거나 동적 할당의 경우 저장소를 통해 암시 적으로 선언 된 메모리 위치에 액세스하려면 해당 유형의 표현식을 사용하여 struct a을 포함해야합니다.

다른 답변에서 인용 된 섹션 중 아무 것도이 기본 제한 사항을 벗어날 수 없습니다.

그러나 문제 해결 방법은 다음과 같습니다. __builtin_memcpy()을 사용하십시오.이 환경은 독립형 환경에서도 사용할 수 있습니다 (manual entry on -fno-builtin 참조). 문제가 내가 소리를 만드는 것보다 잘라 맑은 조금 작다는 것을


참고. C11 섹션 6.5 §7은 우변 표현식을 통해 의 구성원 중 위에서 언급 한 유형 중 하나를 포함하는 집합 또는 합집합 유형을 가진 객체에 액세스하는 것이 좋습니다..

C99의 이론적 근거에 따르면이 제한이 있으므로 집계에 대한 포인터와 그 구성원 중 하나에 대한 포인터가 별칭이 될 수 있습니다.

나는, 의도하지 않은 결과이다 (실제 char [4]를 가리 키도록 발생하지 않습니다 p 가정하지만, 하지 두 번째) 첫 번째 예제의 방법으로이 허점을 사용할 수있는 능력을 믿는 표준 만 부정확 한 말씨 때문에 거부 할 수 없다.

첫 번째 예제가 유효하면 기본적으로 다른 형식으로 입력 된 언어로 구조적인 입력을 몰래 할 수 있습니다. 공통 초기 하위 시퀀스가있는 유니온의 구조 (멤버 이름도 중요 함)와 동일한 메모리 레이아웃만으로는 유형을 호환 할 수 없습니다. 나는 같은 추론이 여기에 적용된다고 생각한다. 두 번째 예에서

+0

memcpy를 사용하는 것은 흥미 롭습니다.'uint32_t i; memcpy (& i, p, 4); i를 되 돌린다. 'gcc는 read32() 함수와 정확히 같은 코드를 생성합니다. x86에서 단 하나의로드. ARM에서는 memcpy (버퍼가 정렬되지 않은 경우)에 대한 호출이 질문에 제공된 read32() 함수에 대해서도 생성됩니다. – nos

0

struct a { char r[4]; }; 

이 구조 유형은 몇 가지의 정렬에 대한 제한이있을 수 있습니다. 컴파일러는 struct a이 항상 4 바이트 정렬됨을 결정할 수 있는데, 예를 들어, 실제 주소를 보지 않고 항상 4 바이트 정렬 판독 명령을 사용할 수있다. 당신이 read32에 인수로받을 포인터 p는 그런 제한이 없기 때문에

*(struct a*)p; 

는 버스 오류가 발생할 수 있습니다.

이 유형의 인수는 "실용적인"것입니다.

표준의 관점에서 볼 때 (struct a*)p이 더 제한적인 정렬 요구 사항이있는 유형으로 변환되는 즉시 UB입니다.

+1

이것이 가능한 문제 일 것이라는 데 동의하지만, 이는 내가 묻는 별칭 문제와 별개입니다.구조체가 정렬 요구 사항을 가질 수있는 경우 구현 정의 및 테스트 가능이며 정렬 실패의 UB는 구조체가 정렬 요구 사항을 갖는 구현에서만 발생합니다. 반면에 앨리어싱 위반이 발생하면 모든 구현에서 동일하게 정의되지 않습니다. –

+0

@R .. 음, 두 번째 접근법이 모든 유형의 이식 가능한 코드에 대해 유효하지 않게됩니다. 또한 첫 번째와 두 번째가 동등한 질문에 대한 주장을 보완합니다. –

3

에일리어싱 규칙을 읽었습니다 (C99, 6.5p7)이 문장의 존재와 :

"재귀 포함한 부재 (중 상기 유형 중, A A subaggregate 포함되거나 연합)의 부재, 또는를 포함하는 전체 또는 조합 형태"

나는 그것이 C 앨리어싱 규칙을 위반하지 않는다고 생각합니다.

하지만 별칭 규칙을 위반하지 않는다는 사실은이 코드 스 니펫이 유효하기에 충분하지 않습니다. 다른 이유로 인해 정의되지 않은 동작이 호출 될 수 있습니다.

(struct s *) &x 

유효한 struct s 객체를 가리 키도록 보장 할 수 없습니다. x의 정렬이 struct 유형의 객체에 적합하다고 가정하더라도 캐스트 후의 결과 포인터는 구조체 객체를 보유 할만큼 큰 공간을 가리 키지 않을 수 있습니다 (struct가 마지막 멤버 다음에 패딩을 가질 수 있음).

편집은 : 대답은 완전히 초기 버전 나에게 매우 위험한 보이는

+0

그게 C11 6.5 §7에 대한 필자의 생각이다. 그러나 [구성원은 그 멤버들 중 위에서 언급 한 타입 중 하나를 포함하는 집합체 또는 합집합 타입을 가진 lvalue 표현식에 의해 액세스되는 저장된 값을 가질 수있다. *) – Christoph

+0

@Christoph : 사실, 당신이 인용 한 텍스트는 제 코드 스 니펫 # 1이 잘 정의되어있을 수 있다고 생각하지만, 스 니펫 # 2는 정의되지 않았습니다 (유효 유형이 실제로'unsigned char [4]' 대신에 다른 타입의 표현에 액세스하려고 시도하고 있습니다. "char"타입에 대한 텍스트가 별칭을 지정할 수 있다는 것은 의도적으로 생각한 것 같습니다. –

+0

@R ..이 해석은'struct bla {long a; float b;}; float y = 0; x = * (struct bla *) & y;'유효하다 (정렬은 OK라고 가정한다.) 이상한 것, 아니? – ouah