비활성 조합원 및 정의되지 않은 동작에 액세스하고 있습니까?


129

union마지막 세트 이외의 멤버에 액세스하는 것이 UB 라는 인상을 받았지만 견실 한 참조를 찾을 수없는 것 같습니다 (UB를 주장하는 답변 외에 표준의 지원이없는 답변).

정의되지 않은 동작입니까?


3
C99 (그리고 C ++ 11도 마찬가지 임)는 명시 적으로 유니온으로 타입 제거를 허용합니다. 그래서 나는 그것이 "구현 정의 된"행동에 속한다고 생각합니다.
신비주의

1
개별 int에서 char로 변환하기 위해 여러 번 사용했습니다. 그래서 나는 그것이 정의되지 않았다는 것을 확실히 알고 있습니다. Sun CC 컴파일러에서 사용했습니다. 따라서 여전히 컴파일러에 따라 다를 수 있습니다.
go4sri

42
@ go4sri : 분명히, 당신은 행동이 정의되지 않았다는 것이 무엇을 의미하는지 모릅니다. 어떤 경우에는 그것이 효과가있는 것처럼 보이지만 정의되지 않은 것과 모순되지 않습니다.
Benjamin Lindley


4
@Mysticial, 귀하가 링크하는 블로그 게시물은 C99에 관한 것입니다. 이 질문은 C ++에만 해당됩니다.
davmac

답변:


131

혼란은 C가 명시 적으로 유니온을 통해 유형 정리를 허용하는 반면 C ++ ()은 그러한 권한이 없습니다.

6.5.2.3 구조와 노조원

95) 공용체 객체의 내용을 읽는 데 사용 된 멤버가 객체에 값을 저장하는 데 사용한 마지막 멤버와 동일하지 않은 경우 값의 객체 표현의 해당 부분이 새 객체의 객체 표현으로 해석됩니다. 6.2.6에 기술 된 유형 (때때로``유형 punning ''이라고하는 프로세스). 이것은 트랩 표현 일 수 있습니다.

C ++의 상황 :

9.5 조합 [class.union]

공용체에서, 비 정적 데이터 멤버 중 최대 하나는 언제든지 활성화 될 수 있습니다. 즉, 비 정적 데이터 멤버 중 최대 하나의 값은 언제든지 통합에 저장 될 수 있습니다.

C ++은 나중에 struct공통 초기 시퀀스를 가진을 포함하는 공용체를 사용할 수있는 언어를 가지고 있습니다 . 그러나 이것은 유형 정리를 허용하지 않습니다.

C ++에서 공용체 유형 제거 허용 되는지 확인하려면 추가로 검색해야합니다. 기억해 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를 구성 할 수 있지만 (건축하지 않고 비 활동 회원에게 할당하는 것이 괜찮은 이유) 초기화되지 않은 것으로 간주됩니다.


5
3.8 / 1은 객체의 수명이 스토리지를 재사용 할 때 종료된다고 말합니다. 즉, 노조 수명의 비 활동 구성원이 해당 구성원의 스토리지가 재사용 되었기 때문에 종료되었음을 나타냅니다. 그것은 당신이 회원을 사용하는 방법에 제한이 있음을 의미합니다 (3.8 / 6).
bames53

2
이 해석에 따르면 모든 메모리 비트에는 동시에 초기화가 가능하고 적절한 정렬을 가진 모든 유형의 객체가 포함됩니다. 따라서 모든 다른 유형에 대해 스토리지가 재사용되면 사소하게 초기화 할 수없는 유형의 수명이 즉시 종료됩니다 ( 사소하게 초기화 할 수 없기 때문에 다시 시작하지 마십시오)?
bames53

3
4.1이라는 문구는 완전히 완전히 깨져서 다시 쓰여졌습니다. 그것은 완벽하게 유효한 모든 종류의 것들을 허용 : 그것은 사용자 정의 허용 memcpy(사용하여 개체에 액세스하는 구현을 unsigned charlvalues을), 그것은에 액세스 허용 *pint *p = 0; const int *const *pp = &p;(에서 암시 적 변환에도 불구하고 int**에가 const int*const*유효), 그것도 접근 허용 c후를 struct S s; const S &c = s;. CWG 문제 616 . 새로운 표현이 그것을 허용합니까? [basic.lval]도 있습니다.

2
@Omifaifarious : &노조원에게 적용될 때 단항 연산자가 의미하는 바를 명확히해야하고 C 표준도 명확하게해야합니다 . 결과 포인터는 다음에 다른 멤버 lvalue를 다음에 직접 또는 간접적으로 사용할 때까지 멤버에 액세스 할 수 있어야한다고 생각하지만 gcc에서는 포인터를 오랫동안 사용할 수 없어서 &연산자는 뜻이다.
supercat

4
에 관한 질문 중 하나는 "리콜 C99 (11) C ++에 대한 규범 적 기준이다" 되지 않는 C ++ 표준은 명시 적으로 (예를 들어 C 라이브러리 함수의 경우) C 표준을 의미 경우에만 관련?
MikeMB

28

C ++ 11 표준은 이렇게 말합니다

9.5 조합

공용체에서, 비 정적 데이터 멤버 중 최대 하나는 언제든지 활성화 될 수 있습니다. 즉, 비 정적 데이터 멤버 중 최대 하나의 값은 언제든지 통합에 저장 될 수 있습니다.

하나의 값만 저장된 경우 다른 값을 어떻게 읽을 수 있습니까? 그냥 거기에 없습니다.


gcc 문서는 이것을 구현 정의 동작 아래에 나열합니다.

  • 다른 유형의 멤버 (C90 6.3.2.3)를 사용하여 통합 개체의 멤버에 액세스합니다.

객체 표현의 관련 바이트는 액세스에 사용되는 유형의 객체로 취급됩니다. 유형 제거를 참조하십시오. 이것은 트랩 표현 일 수 있습니다.

이는 C 표준에 필요하지 않음을 나타냅니다.


2016-01-05 : 의견을 통해 C 표준 문서에 각주와 유사한 텍스트를 추가하는 C99 결함 보고서 # 283 에 연결되었습니다 .

78a) 공용체 객체의 내용에 액세스하는 데 사용 된 멤버가 객체에 값을 저장하는 데 사용한 마지막 멤버와 동일하지 않은 경우 값의 객체 표현의 해당 부분이 새 객체의 객체 표현으로 해석됩니다. 6.2.6에 기술 된 유형 (종종 "유형 punning"이라고하는 프로세스). 이것은 트랩 표현 일 수 있습니다.

각주가 표준에 대한 규범이 아니라는 점을 고려하면 많은 설명이 있는지 확실하지 않습니다.


10
@LuchianGrigore : UB는 UB가 표준이라고 말한 것이 아니라 표준이 작동 방식을 설명하지 않는 것입니다. 이것은 정확히 그런 경우입니다. 표준은 무슨 일이 일어나는가? 구현이 정의되었다고 말합니까? 아냐 아냐 UB입니다. 또한 "멤버가 동일한 메모리 주소를 공유합니다"인수와 관련하여 앨리어싱 규칙을 참조해야하므로 다시 UB로 이동합니다.
Yakov Galka

5
@Luchian : "정적이 아닌 데이터 멤버 중 최대 하나의 값을 언제든지 유니언에 저장할 수 있습니다."
Benjamin Lindley

5
@LuchianGrigore : 그렇습니다. 표준이 다루지 않는 (그리고 할 수없는) 경우는 무한하다. (C ++은 Turing 완전한 VM이므로 불완전합니다.) 그렇다면 무엇입니까? "활성"의 의미를 설명하고, "즉"뒤에있는 위 인용문을 참조하십시오.
Yakov Galka

8
@LuchianGrigore : 정의 섹션에 따르면 명시 적 동작 정의 생략도 정의되지 않은 동작으로 간주되지 않습니다.
jxh

5
@Claudiu 그것은 다른 이유로 UB입니다. 엄격한 앨리어싱을 위반합니다.
신비주의

18

나는 표준이 정의되지 않은 행동이라고 말하는 가장 가까운 것은 공통 초기 시퀀스 (C99, §6.5.2.3 / 5)를 포함하는 노동 조합의 행동을 정의하는 위치라고 말합니다.

공용체 사용을 단순화하기 위해 하나의 특별한 보증이 있습니다. 공용체에 공통 초기 시퀀스를 공유하는 여러 구조가 포함되어 있고 (아래 참조) 공용체 개체에 현재 이러한 구조 중 하나가 포함되어 있으면 공용체를 검사 할 수 있습니다. 완전한 유형의 공용체 선언이 보이는 곳이면 어디서나 초기 부분. 대응하는 멤버가 하나 이상의 초기 멤버 시퀀스에 대해 호환 가능한 유형 (및 비트 필드의 경우 동일한 너비)을 갖는 경우 두 구조가 공통 초기 시퀀스를 공유합니다.

C ++ 11은 §9.2 / 19에서 비슷한 요구 사항 / 권한을 제공합니다.

표준 레이아웃 공용체에 공통 초기 시퀀스를 공유하는 둘 이상의 표준 레이아웃 구조체가 포함되어 있고 표준 레이아웃 공용체 객체에 현재 이러한 표준 레이아웃 구조체 중 하나가 포함되어있는 경우 표준 레이아웃 공용체의 공통 초기 부분을 검사 할 수 있습니다 그들의. 대응하는 멤버가 레이아웃 호환 유형을 가지고 있고 멤버가 비트 필드이거나 둘 다 하나 이상의 초기 멤버 시퀀스에 대해 동일한 너비를 가진 비트 필드 인 경우 두 표준 레이아웃 구조체는 공통 초기 시퀀스를 공유합니다.

직접적으로 언급하지는 않지만 1) 회원이 (최근에 쓴 회원의 일부이거나) 2) 공통 이니셜의 일부인 경우 에만 "검사"(읽기) 회원이 "허가"되었다는 강한 의미를 지니고 있습니다. 순서.

그것은 달리 정의하는 것이 정의되지 않은 행동이라는 직접적인 진술은 아니지만 내가 아는 가장 가까운 것입니다.


이 작업을 완료하려면 C ++의 "레이아웃 호환 유형"또는 C의 "호환 유형"이 무엇인지 알아야합니다.
Michael Anderson

2
@MichaelAnderson : 그렇습니다. 무언가 예외가이 예외에 해당되는지 확실하게 / 확실하게 원하는 경우이를 처리해야합니다. 그러나 여기서 실제 질문은 예외를 벗어나는 것이 실제로 UB를 제공하는지 여부입니다. 나는 그것이 의도를 분명히하기 위해 여기에 강력하게 암시되어 있다고 생각하지만, 그것이 직접 언급 된 것은 아니라고 생각합니다.
Jerry Coffin

이 "일반적인 초기 시퀀스"는 Rewrite Bin에서 2 ~ 3 개의 프로젝트를 저장했을 것입니다. union특정 블로그에서 이것이 괜찮다는 인상을 받았으며 주변에 여러 개의 큰 구조와 프로젝트를 만들었 기 때문에 정의되지 않은 s의 대부분의 사용에 대해 처음 읽었을 때 나는 생생 했습니다. 내 생각 엔union 앞면에 같은 유형의 수업이 포함되어 있기 때문에 결국 괜찮을 것 같습니다.
underscore_d

@ JerryCoffin, 나는 당신이 나와 같은 질문을 암시하고 있다고 생각합니다 : 예를 들어 a 와 a를 union포함 한다면 어떻게 될까요? 이 단서가 여기에도 적용된다고 가정하지만 s를 허용하는 것은 매우 의도적으로 표현되었습니다 . 운 좋게도 나는 원시 원시 대신에 이미 그것들을 사용하고 있습니다 : Ouint8_tclass Something { uint8_t myByte; [...] };struct
underscore_d

@underscore_d : C 표준은 최소한 다음과 같은 질문을 다룹니다. "적절하게 변환 된 구조 객체에 대한 포인터는 초기 멤버를 가리 킵니다 (또는 해당 멤버가 비트 필드 인 경우 해당 필드가있는 단위를 가리킴) , 그 반대."
Jerry Coffin

12

이용 가능한 답변에서 아직 언급되지 않은 부분은 6.2.5 섹션 21의 각주 37입니다.

통합 유형의 개체는 한 번에 하나의 멤버 만 포함 할 수 있으므로 집계 유형에는 통합 유형이 포함되지 않습니다.

이 요구 사항은 귀하가 회원을 작성하거나 다른 회원을 읽지 말아야한다는 것을 분명히 암시하는 것 같습니다. 이 경우 사양이 부족하여 정의되지 않은 동작 일 수 있습니다.


많은 구현에서 스토리지 형식 및 레이아웃 규칙을 문서화합니다. 이러한 사양은 많은 경우 한 유형의 스토리지를 읽고 다른 유형으로 쓰면 어떤 점이 컴파일러가 포인터를 사용하여 물건을 읽고 쓸 때를 제외하고는 정의 된 스토리지 형식을 실제로 사용할 필요가 없다는 규칙이 없을 때의 영향을 암시합니다. 문자 유형.
supercat

-3

나는 이것을 예를 들어 잘 설명한다.
우리는 다음과 같은 합동이 있다고 가정합니다.

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이있는 경우 정확한 숫자를 저장하는 방식으로 인해 메모리 맵이 엉망이됩니다. 더 많은 정보는 여기에 얻을 수 있습니다 .


18
:) 이것은 내가 요구 한 것이 아니다. 내부적으로 어떤 일이 발생하는지 알고 있습니다. 나는 그것이 작동한다는 것을 안다. 나는 그것이 표준인지 물었다.
Luchian Grigore
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.