union
마지막 세트 이외의 멤버에 액세스하는 것이 UB 라는 인상을 받았지만 견실 한 참조를 찾을 수없는 것 같습니다 (UB를 주장하는 답변 외에 표준의 지원이없는 답변).
정의되지 않은 동작입니까?
답변
혼란은 C가 명시 적으로 유니온을 통해 유형 정리를 허용하는 반면 C ++ (C ++ 11)은 그러한 권한이 없습니다.
6.5.2.3 구조와 노조원
95) 공용체 객체의 내용을 읽는 데 사용 된 멤버가 객체에 값을 저장하는 데 사용한 마지막 멤버와 동일하지 않은 경우 값의 객체 표현의 해당 부분이 새 객체의 객체 표현으로 해석됩니다. 6.2.6에 기술 된 유형 (때때로“유형 punning ”이라고하는 프로세스). 이것은 트랩 표현 일 수 있습니다.
C ++의 상황 :
9.5 조합 [class.union]
공용체에서, 비 정적 데이터 멤버 중 최대 하나는 언제든지 활성화 될 수 있습니다. 즉, 비 정적 데이터 멤버 중 최대 하나의 값은 언제든지 통합에 저장 될 수 있습니다.
C ++은 나중에 struct
공통 초기 시퀀스를 가진을 포함하는 공용체를 사용할 수있는 언어를 가지고 있습니다 . 그러나 이것은 유형 정리를 허용하지 않습니다.
C ++에서 공용체 유형 제거 가 허용 되는지 확인하려면 추가로 검색해야합니다. 기억해c99 C ++ 11에 대한 규범적인 참조입니다 (그리고 C99는 C11과 비슷한 언어를 사용하여 공용체 유형 제거를 허용합니다).
3.9 유형 [기본 유형]
4-T 유형의 오브젝트의 오브젝트 표시는 T 유형의 오브젝트가 차지하는 N 개의 부호없는 char 오브젝트의 시퀀스입니다. 여기서 N은 sizeof (T)와 같습니다. 객체의 값 표현은 유형 T의 값을 보유하는 비트 세트입니다. 사소하게 복사 가능한 유형의 경우, 값 표현은 값을 결정하는 객체 표현의 비트 세트입니다. 이는 구현의 개별 요소입니다. 정의 된 값 집합. 42
42) C ++의 메모리 모델은 ISO / IEC 9899 프로그래밍 언어 C의 메모리 모델과 호환됩니다.
읽을 때 특히 흥미 롭습니다
3.8 객체 수명 [basic.life]
T 유형의 객체 수명은 다음과 같은 경우에 시작됩니다.
따라서 공용체에 포함 된 기본 유형 ( ipso 사실상 간단한 초기화가 있음)의 경우 객체의 수명은 최소한 연합 자체의 수명을 포함합니다. 이것은 우리가 호출 할 수 있습니다
3.9.2 화합물 유형 [기본 화합물]
T 유형의 개체가 주소 A에있는 경우 값이 주소 A 인 cv T * 유형의 포인터는 값을 얻는 방법에 관계없이 해당 개체를 가리킨다 고합니다.
우리가 관심있는 작업이 type-punning이라고 가정합니다. 즉, 비 활동 노조원의 가치를 취하는 것으로 가정하면, 그에 따라 해당 회원이 참조하는 객체에 대한 유효한 참조가 있다고 위에서 말하면, 그 작업은 lvalue-to -r 값 변환 :
4.1 L 값에서 R 값으로의 변환 [전환]
비 기능 비 배열 유형
T
의 glvalue는 prvalue로 변환 될 수 있습니다. 경우T
불완전한 유형, 잘못 형성되는이 변환을 필요로하는 프로그램입니다. glvalue가 참조하는 객체가 유형의 객체T
가 아니며에서 파생 된 유형의 객체가 아니T
거나 객체가 초기화되지 않은 경우이 변환이 필요한 프로그램은 정의되지 않은 동작을합니다.
문제는 비 활동 공용체 구성원 인 오브젝트가 활성 공용체 구성원에 저장하여 초기화되는지 여부입니다. 내가 알 수있는 한, 이것은 사실이 아니므로 다음과 같은 경우에도 마찬가지입니다.
- 공용체가
char
배열 스토리지 로 복사되어 다시 복사 되거나 (3.9 : 2) - 공용체가 동일한 유형 (3.9 : 3)의 다른 공용체에 바이트 단위로 복사되거나
- ISO / IEC 9899 (정의 된 한) (3.9 : 4 주 42)를 따르는 프로그램 요소에 의해 언어 경계를 넘어서 연합에 접근 한 후
비 활동 구성원에 의한 통합에 대한 액세스 가 정의 되고 오브젝트 및 값 표시를 따르도록 정의되며, 위의 개입 중 하나가없는 액세스는 정의되지 않은 동작입니다. 구현은 물론 정의되지 않은 동작이 발생하지 않는다고 가정 할 수 있기 때문에 이는 그러한 프로그램에서 수행 될 수있는 최적화에 영향을 미칩니다.
즉, 우리는 비 활동 조합원에게 합법적으로 lvalue를 구성 할 수 있지만 (건축하지 않고 비 활동 회원에게 할당하는 것이 괜찮은 이유) 초기화되지 않은 것으로 간주됩니다.
답변
C ++ 11 표준은 이렇게 말합니다
9.5 조합
공용체에서, 비 정적 데이터 멤버 중 최대 하나는 언제든지 활성화 될 수 있습니다. 즉, 비 정적 데이터 멤버 중 최대 하나의 값은 언제든지 통합에 저장 될 수 있습니다.
하나의 값만 저장된 경우 다른 값을 어떻게 읽을 수 있습니까? 그냥 거기에 없습니다.
gcc 문서는 이것을 구현 정의 동작 아래에 나열합니다.
- 다른 유형의 멤버 (C90 6.3.2.3)를 사용하여 통합 개체의 멤버에 액세스합니다.
객체 표현의 관련 바이트는 액세스에 사용되는 유형의 객체로 취급됩니다. 유형 제거를 참조하십시오. 이것은 트랩 표현 일 수 있습니다.
이는 C 표준에 필요하지 않음을 나타냅니다.
2016-01-05 : 의견을 통해 C 표준 문서에 각주와 유사한 텍스트를 추가하는 C99 결함 보고서 # 283 에 연결되었습니다 .
78a) 공용체 객체의 내용에 액세스하는 데 사용 된 멤버가 객체에 값을 저장하는 데 사용한 마지막 멤버와 동일하지 않은 경우 값의 객체 표현의 해당 부분이 새 객체의 객체 표현으로 해석됩니다. 6.2.6에 기술 된 유형 (종종 “유형 punning”이라고하는 프로세스). 이것은 트랩 표현 일 수 있습니다.
각주가 표준에 대한 규범이 아니라는 점을 고려하면 많은 설명이 있는지 확실하지 않습니다.
답변
나는 표준이 정의되지 않은 행동이라고 말하는 가장 가까운 것은 공통 초기 시퀀스 (C99, §6.5.2.3 / 5)를 포함하는 노동 조합의 행동을 정의하는 위치라고 말합니다.
공용체 사용을 단순화하기 위해 하나의 특별한 보증이 있습니다. 공용체에 공통 초기 시퀀스를 공유하는 여러 구조가 포함되어 있고 (아래 참조) 공용체 개체에 현재 이러한 구조 중 하나가 포함되어 있으면 공용체를 검사 할 수 있습니다. 완전한 유형의 공용체 선언이 보이는 곳이면 어디서나 초기 부분. 대응하는 멤버가 하나 이상의 초기 멤버 시퀀스에 대해 호환 가능한 유형 (및 비트 필드의 경우 동일한 너비)을 갖는 경우 두 구조가 공통 초기 시퀀스를 공유합니다.
C ++ 11은 §9.2 / 19에서 비슷한 요구 사항 / 권한을 제공합니다.
표준 레이아웃 공용체에 공통 초기 시퀀스를 공유하는 둘 이상의 표준 레이아웃 구조체가 포함되어 있고 표준 레이아웃 공용체 객체에 현재 이러한 표준 레이아웃 구조체 중 하나가 포함되어있는 경우 표준 레이아웃 공용체의 공통 초기 부분을 검사 할 수 있습니다 그들의. 대응하는 멤버가 레이아웃 호환 유형을 가지고 있고 멤버가 비트 필드이거나 둘 다 하나 이상의 초기 멤버 시퀀스에 대해 동일한 너비를 가진 비트 필드 인 경우 두 표준 레이아웃 구조체는 공통 초기 시퀀스를 공유합니다.
직접적으로 언급하지는 않지만 1) 회원이 (최근에 쓴 회원의 일부이거나) 2) 공통 이니셜의 일부인 경우 에만 “검사”(읽기) 회원이 “허가”되었다는 강한 의미를 지니고 있습니다. 순서.
그것은 달리 정의하는 것이 정의되지 않은 행동이라는 직접적인 진술은 아니지만 내가 아는 가장 가까운 것입니다.
답변
이용 가능한 답변에서 아직 언급되지 않은 부분은 6.2.5 섹션 21의 각주 37입니다.
통합 유형의 개체는 한 번에 하나의 멤버 만 포함 할 수 있으므로 집계 유형에는 통합 유형이 포함되지 않습니다.
이 요구 사항은 귀하가 회원을 작성하거나 다른 회원을 읽지 말아야한다는 것을 분명히 암시하는 것 같습니다. 이 경우 사양이 부족하여 정의되지 않은 동작 일 수 있습니다.
답변
나는 이것을 예를 들어 잘 설명한다.
우리는 다음과 같은 합동이 있다고 가정합니다.
union A{
int x;
short y[2];
};
나는 물론 그 가정 sizeof(int)
(4)를 제공하고, 그 sizeof(short)
2주는
당신이 쓸 때 union A a = {10}
잘 값 (10)에 넣어에서 A 형의 새로운 VAR을 만드는 것이.
당신의 기억은 다음과 같아야합니다 : (모든 노조원이 같은 위치를 차지한다는 것을 기억하십시오)
| x | | y [0] | y [1] | ----------------------------------------- a-> | 0000 0000 | 0000 0000 | 0000 0000 | 0000 1010 | -----------------------------------------
보시다시피, ax의 값은 10이고, ay 1 의 값 은 10이며, ay [0]의 값은 0입니다.
이제이 작업을 수행하면 어떻게됩니까?
a.y[0] = 37;
우리의 기억은 다음과 같습니다 :
| x | | y [0] | y [1] | ----------------------------------------- a-> | 0000 0000 | 0010 0101 | 0000 0000 | 0000 1010 | -----------------------------------------
그러면 ax 값이 2424842 (10 진수)로 바뀝니다.
이제 유니온에 float 또는 double이있는 경우 정확한 숫자를 저장하는 방식으로 인해 메모리 맵이 엉망이됩니다. 더 많은 정보는 여기에 얻을 수 있습니다 .