C와 C ++에서 연합의 목적


254

나는 이전에 편하게 노동 조합을 사용했다. 오늘 이 게시물을 읽었을 때 놀랐 으며이 코드를 알게되었습니다.

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

실제로 정의되지 않은 동작입니다. 즉 최근에 작성된 조합 이외의 조합 구성원에서 읽는 것은 정의되지 않은 동작으로 이어집니다. 이것이 노조의 의도 된 사용법이 아닌 경우 무엇입니까? 어떤 사람은 정교하게 설명해 주시겠습니까?

최신 정보:

나는 몇 가지를 명확하게 설명하고 싶었다.

  • 질문에 대한 대답은 C와 C ++에서 동일하지 않습니다. 내 무식한 젊은 자아는 그것을 C와 C ++로 태그했습니다.
  • C ++ 11의 표준을 꼼꼼히 살펴본 후에는 비활성 조합 멤버에 대한 액세스 / 검사가 정의되지 않음 / 지정되지 않음 / 구현이 정의되었다고 결론을 내릴 수 없었습니다. 내가 찾을 수있는 것은 §9.5 / 1입니다.

    표준 레이아웃 공용체에 공통 초기 시퀀스를 공유하는 여러 표준 레이아웃 구조체가 포함되어 있고이 표준 레이아웃 공용체 유형의 오브젝트에 표준 레이아웃 구조체 중 하나가 포함 된 경우 모든 표준의 일반 시퀀스를 검사 할 수 있습니다 표준 레이아웃 구조체 멤버 §9.2 / 19 : 대응하는 멤버가 레이아웃 호환 유형을 가지고 있고 멤버가 비트 필드이거나 둘 다 동일한 너비를 가진 비트 필드가 하나 이상의 초기 시퀀스 인 경우 두 개의 표준 레이아웃 구조체가 공통 초기 시퀀스를 공유합니다. 회원.

  • C ( C99 TC3-DR 283 이상)에서는 그렇게하는 것이 합법적입니다 ( Pascal Cuoq 덕분에 ). 그러나 읽은 유형에 대해 읽은 값이 유효하지 않은 경우 ( "트랩 표시"라고 함) 이를 수행하려고 해도 여전히 정의되지 않은 동작이 발생할 수 있습니다. 그렇지 않으면 읽은 값이 구현 정의됩니다.
  • C89 / 90은 지정되지 않은 행동 (Annex J) 하에서 이것을 호출했으며 K & R의 책은 구현이 정의되었다고 말합니다. K & R에서 인용 :

    이는 여러 유형 중 하나를 합법적으로 보유 할 수있는 단일 변수 인 공용체의 목적입니다. 사용법이 일관된 한 [...] : 검색된 유형은 가장 최근에 저장된 유형이어야합니다. 현재 어떤 유형이 공용체에 저장되어 있는지 추적하는 것은 프로그래머의 책임입니다. 어떤 것이 한 유형으로 저장되고 다른 유형으로 추출되면 결과는 구현에 따라 다릅니다.

  • Stroustrup의 TC ++ PL (강조 광산)에서 추출

    노동 조합의 사용은 데이터 [...]의 compatness에 필수적 일 수있다 때로는 "유형 변환에 잘못 ".

무엇보다도,이 질문하지 표준이 허용하는 것에 노동 조합의 목적을 이해하는 의도로 제기되었다 (그 제목은 내가 물어 이후 변경되지 않습니다) 물론, C ++ 표준에 의해 허용되는 예는 코드 재사용에 대한 상속을 사용하지만, 상속을 C ++ 언어 기능으로 도입하려는 의도 나 원래 의도는 아니었다 . 이것이 Andrey의 대답이 계속 받아 들여지는 이유입니다.


11
간단히 말해서 컴파일러는 구조체의 요소 사이에 패딩을 삽입 할 수 있습니다. 따라서, b, g, r,a인접하지 않을 수 있으며, 따라서 (A)의 레이아웃과 일치하지 uint32_t. 이것은 다른 사람들이 지적한 Endianess 문제에 추가됩니다.
Thomas Matthews

8
이것이 바로 질문 C 및 C ++에 태그를 지정해서는 안되는 이유입니다. 답은 다르지만, 응답자는 어떤 태그에 응답하고 있는지조차 알지 못하기 때문에 (아마도 아는가?) 쓰레기를 얻습니다.
Pascal Cuoq

5
@downvoter 설명 해주셔서 감사합니다. 저는 그립을 마술처럼 이해하고 앞으로도 반복하지
않기를

1
노동 조합 의 원래 의도와 관련하여 C 표준은 C 노동 조합에 몇 년이 지난다는 사실을 명심해야한다. Unix V7을 간단히 살펴보면 유니온을 통한 몇 가지 유형 변환이 표시됩니다.
ninjalj

3
scouring C++11's standard I couldn't conclusively say that it calls out accessing/inspecting a non-active union member is undefined [...] All I could find was §9.5/1...정말? 단락 시작 부분의 주요 요점이 아니라 예외 메모 를 인용하면 다음과 같습니다. "유니온에서는 정적이 아닌 데이터 멤버 중 대부분이 언제든지 활성화 될 수 있습니다. 즉, 최대 하나의 값 비 정적 데이터 멤버는 언제든지 통합에 저장할 수 있습니다. " -p4까지 : "일반적으로 명시 적 소멸자 호출 및 배치 새 연산자를 사용하여 조합의 활성 멤버를 변경해야합니다 "
underscore_d

답변:


407

노조의 목적은 다소 분명하지만 어떤 이유로 사람들은 종종 그것을 그리워합니다.

통합의 목적은 서로 다른 시간에 서로 다른 객체를 저장하기 위해 동일한 메모리 영역을 사용하여 메모리를 절약 하는 것입니다. 그게 다야.

호텔의 방과 같습니다. 다른 사람들이 겹치지 않는 기간 동안 그 안에 살고 있습니다. 이 사람들은 절대 만나지 않으며 일반적으로 서로에 대해 아무것도 모릅니다. 방의 시분할을 적절히 관리함으로써 (즉, 다른 사람들이 동시에 한 방에 배정되지 않도록함으로써) 상대적으로 작은 호텔은 비교적 많은 수의 사람들에게 숙박 시설을 제공 할 수 있습니다. 입니다.

그것이 바로 노조가하는 일입니다. 프로그램의 여러 개체가 겹치지 않는 값 수명으로 값을 보유한다는 것을 알고 있으면 이러한 개체를 공용체로 "병합"하여 메모리를 절약 할 수 있습니다. 호텔 방마다 매 순간마다 하나의 "활성"테넌트가있는 것처럼, 조합에는 프로그램 시간마다 최대 하나의 "활성"멤버가 있습니다. "활성"멤버 만 읽을 수 있습니다. 다른 회원에게 편지를 쓰면 "활성"상태가 다른 회원으로 전환됩니다.

어떤 이유로 노조의 원래 목적은 완전히 다른 무언가로 "재정의"되었습니다. 한 노조 구성원을 작성한 다음 다른 구성원을 통해 조사합니다. 이런 종류의 메모리 해석 (일명 "유형 punning")은 유효한 공용체 사용아닙니다. 일반적으로 정의되지 않은 동작 은 C89 / 90에서 구현 정의 동작을 생성하는 것으로 설명됩니다.

편집 : punning 유형의 목적으로 유니온을 사용하는 것 (즉, 한 멤버를 작성한 다음 다른 멤버를 읽는 것)은 기술 코리 겐다 중 하나에서 C99 표준에 대한 더 자세한 정의를 얻었습니다 ( DR # 257DR # 283 참조 ). 그러나 공식적으로 이것은 트랩 표현을 읽으려고 시도하여 정의되지 않은 동작이 발생하는 것을 방지하지는 않습니다.


37
정교한 실제 사례를 제시하고 노조의 유산에 대해 이야기하면서 +1하십시오!
legends2k

6
이 답변에 대한 문제는 내가 본 대부분의 OS 에이 정확한 작업을 수행하는 헤더 파일이 있다는 것입니다. 예를 들어 <time.h>Windows와 Unix 모두 에서 이전 (64 비트 이전) 버전에서 보았습니다 . "정확하지 않은"및 "정의되지 않은"것으로 무시하는 것만으로는 이러한 방식으로 작동하는 코드를 이해해야 할 것입니다.
TED

31
@AndreyT“최근까지 유형 정리에 노조를 사용하는 것은 결코 합법적이지 않았습니다.”: 2004 년은“최근”이 아니며, 특히 처음에는 서투른 말로 표현 된 C99만이 노조를 통해 유형 정리를 정의하지 않은 것처럼 보이기 때문에 특히 그렇습니다. 실제로 C89에서는 노조가 합법적이지만 C11에서는 합법적이며 C99에서는 합법적이지만 C99에서는위원회가 잘못된 문구를 수정하고 후속 TC3을 릴리스하는 데 2004 년이 걸렸습니다. open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
Pascal Cuoq

6
@ legends2k 프로그래밍 언어는 표준에 의해 정의됩니다. C99 표준의 Technical Corrigendum 3은 각주 82에 유형 고정을 명시 적으로 허용합니다. 이것은 록 스타들이 인터뷰를하는 TV가 아니며 기후 변화에 대한 의견을 표현합니다. Stroustrup의 의견은 C 표준이 말하는 것에 전혀 영향을 미치지 않습니다.
Pascal Cuoq

6
@ legends2k " 개인의 의견은 중요하지 않으며 표준 만이 중요하다는 것을 알고 있습니다. "컴파일러 작성자의 의견은 (매우 열악한) 언어 "사양"보다 훨씬 중요합니다.
curiousguy

38

공용체를 사용하여 다음과 같은 구조체를 만들 수 있습니다. 여기에는 실제로 사용되는 공용체의 구성 요소를 알려주는 필드가 포함되어 있습니다.

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;

나는 정의되지 않은 행동의 혼돈에 빠지지 않고, 이것이 내가 생각할 수있는 가장 좋은 의도 된 행동 일 것이다. 그러나 10 개의 항목을 사용 int하거나 말 하거나 사용할 때 공간이 낭비되지는 않습니다 char*[]; 어떤 경우에는 실제로 VAROBJECT 대신 각 데이터 유형에 대해 별도의 구조체를 선언 할 수 있습니까? 혼란을 줄이고 공간을 적게 사용하지 않습니까?
legends2k

3
범례 : 어떤 경우에는 단순히 그렇게 할 수 없습니다. Java에서 Object를 사용할 때와 같은 경우 C에서 VAROBJECT와 같은 것을 사용합니다.
Erich Kitzmueller

당신이 설명하는 것처럼 태그붙은 노조 의 데이터 구조는 노조 를 합법적으로 사용하는 것 같습니다.
legends2k

또한 값을 사용하는 방법에 대한 예를 제공하십시오.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功C ++ Primer 예제의 일부 가 도움이 될 수 있습니다. wandbox.org/permlink/cFSrXyG02vOSdBk2
Rick

34

언어 관점에서는 동작이 정의되지 않습니다. 플랫폼마다 메모리 정렬 및 엔디안에 다른 제약 조건이있을 수 있습니다. 빅 엔디안 대 리틀 엔디안 컴퓨터의 코드는 구조체의 값을 다르게 업데이트합니다. 언어에서 동작을 수정하려면 모든 구현에서 동일한 엔디안 (및 메모리 정렬 제약 조건 ...) 제한 사용을 사용해야합니다.

C ++ (두 개의 태그를 사용하고 있음)을 사용하고 실제로 이식성에 관심이 있다면 구조체를 사용하고 세터를 제공 할 수 있습니다. uint32_t 비트 마스크 연산을 통해 필드를 적절하게 설정하고 설정 됩니다. C에서도 함수를 사용하여 동일한 작업을 수행 할 수 있습니다.

편집 : AProgrammer가 투표에 대한 답변을 작성하고 닫을 것으로 기대했습니다. 일부 의견에서 지적했듯이 엔디안은 각 구현이 수행 할 작업을 결정하도록하여 표준의 다른 부분에서 처리되며 정렬 및 패딩도 다르게 처리 할 수 ​​있습니다. 이제 AProgrammer가 암시 적으로 참조하는 엄격한 앨리어싱 규칙이 여기서 중요한 포인트입니다. 컴파일러는 변수의 수정 (또는 수정 부족)을 가정 할 수 있습니다. 공용체의 경우 컴파일러는 명령어를 재정렬하고 각 색상 구성 요소의 읽기를 색상 변수에 대한 쓰기로 이동할 수 있습니다.


명확하고 간단한 답변을 위해 +1! 나는 이식성을 위해, 당신이 2 번째 문단에서 제공 한 방법이 훌륭하다는 데 동의합니다. 그러나 코드가 단일 아키텍처에 묶여 있다면 (protability 가격을 지불하여) 각 픽셀 값에 대해 4 바이트를 절약하고 해당 기능을 실행하는 데 시간이 절약되므로 질문에 넣은 방식을 사용할 수 있습니다 ?
legends2k

엔디안 문제는 표준이 정의되지 않은 동작으로 선언하도록 강요하지 않습니다. reinterpret_cast는 정확히 동일한 엔디안 문제가 있지만 구현에 동작이 정의되어 있습니다.
JoeG

1
@ legends2k, 문제는 옵티마이 저가 uint32_t를 uint8_t에 쓰는 것으로 수정되지 않았다고 가정 할 수 있으므로 최적화 된 가정을 사용할 때 잘못된 값을 얻습니다 ... @ Joe, 액세스 할 때 정의되지 않은 동작이 나타납니다 포인터 (예외가 있습니다).
AProgrammer

1
@ legends2k / AProgrammer : reinterpret_cast의 결과는 구현 정의입니다. 반환 된 포인터를 사용하면 정의되지 않은 동작이 발생하지 않으며 구현에서 정의 된 동작 만 발생합니다. 다시 말해서, 행동은 일관되고 정의되어야하지만 이식성이 없습니다.
JoeG

1
@ legends2k : 괜찮은 최적화 프로그램은 전체 바이트를 선택하고 바이트를 읽거나 쓰는 코드를 생성하는 비트 단위 연산을 유니온과 동일하지만 잘 정의 된 (및 휴대용) 인식합니다. 예를 들어 uint8_t getRed () const {return color & 0x000000FF; } void setRed (uint8_t r) {color = (컬러 & ~ 0x000000FF) | 아르 자형; }
Ben Voigt

22

내가 자주 사용 하는 가장 일반적인 용도 union앨리어싱 입니다.

다음을 고려하세요:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}

이것은 무엇을 하는가? 다음 중 하나의 이름으로 님 Vector3f vec;의 멤버에 깔끔하고 깔끔하게 액세스 할 수 있습니다 .

vec.x=vec.y=vec.z=1.f ;

또는 배열에 대한 정수 액세스

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

경우에 따라 이름으로 액세스하는 것이 가장 명확한 일입니다. 다른 경우, 특히 프로그래밍 방식으로 축을 선택하는 경우 x의 경우 0, y의 경우 1, z의 경우 2의 숫자 인덱스로 축에 액세스하는 것이 더 쉽습니다.


3
이것은 또한 type-punning질문에서 언급되는 것입니다. 또한 질문의 예는 비슷한 예를 보여줍니다.
legends2k

4
punning 유형이 아닙니다. 내 예제에서는 유형이 일치 하므로 "pun"이 없으며 단순히 별칭 일뿐입니다.
bobobobo

3
그렇습니다. 그러나 여전히 언어 표준의 절대적인 관점에서, 읽고 쓰는 구성원이 다르므로 질문에서 언급 한 바와 같이 정의되지 않습니다.
legends2k

3
미래의 표준이이 공통적 인 사례가 "공통 초기 하위 순서"규칙 하에서 허용되도록 수정하기를 희망합니다. 그러나 배열은 현재 문구에서 해당 규칙에 참여하지 않습니다.
Ben Voigt

3
@curiousguy : 구조체 멤버를 임의의 패딩없이 배치 할 필요는 없습니다. 구조체 멤버 배치 또는 구조체 크기에 대한 코드 테스트 인 경우 액세스가 공용체를 통해 직접 수행되는 경우 코드가 작동해야하지만 표준을 엄격하게 읽으면 공용체 또는 구조체 멤버의 주소를 가져 가면 사용할 수없는 포인터가 생성됨을 나타냅니다 자체 유형의 포인터로 사용되지만 먼저 둘러싸는 유형 또는 문자 유형에 대한 포인터로 다시 변환해야합니다. 원격으로 작동 가능한 컴파일러는 다음보다 더 많은 작업을 수행하여 언어를 확장합니다.
supercat

10

당신이 말했듯이, 이것은 많은 플랫폼에서 "작동"하지만 엄격하게 정의되지 않은 동작입니다. 공용체를 사용하는 실제 이유는 변형 레코드를 만드는 것입니다.

union A {
   int i;
   double d;
};

A a[10];    // records in "a" can be either ints or doubles 
a[0].i = 42;
a[1].d = 1.23;

물론 변형이 실제로 포함하는 것을 말해주는 일종의 차별자가 필요합니다. 그리고 C ++에서는 공용체가 POD 유형 만 포함 할 수 있기 때문에 많이 사용되지 않습니다. 실제로 생성자와 소멸자가없는 POD 유형 만 있습니다.


(질문과 같이) 그렇게 사용 했습니까? :)
legends2k

약간 비관적이지만 "변형 기록"을 받아들이지 않습니다. 즉, 그들이 염두에두고 있다고 확신하지만, 그들이 우선 순위라면 왜 제공하지 않습니까? "다른 것들도 구축하는 것이 유용 할 수 있기 때문에 빌딩 블록을 제공하십시오"는 직관적으로 보입니다. 입력 및 출력 레지스터 (중복되는 동안)가 고유 한 이름, 유형 등을 가진 별개의 엔티티 인 메모리 매핑 된 I / O 레지스터-기억해야 할 응용 프로그램이 하나 이상 있습니다.
Steve314

@ Stev314 그것이 그들이 생각했던 용도라면, 그들은 그것을 정의되지 않은 행동으로 만들 수 없었을 것입니다.

@Neil : +1은 정의되지 않은 동작에 영향을주지 않으면 서 실제 사용량에 대해 첫 번째로 말합니다. 다른 유형의 punning 작업 (reinterpret_cast 등)과 같이 구현을 구현했을 수 있다고 생각합니다. 그러나 내가 물어 본 것처럼, 당신은 타이핑에 사용 했습니까?
legends2k

@Neil-메모리 매핑 된 레지스터 예제는 정의되지 않았으며 일반적인 엔디안 등을 제외하고 "휘발성"플래그가 지정되었습니다. 이 모델에서 주소에 쓰는 것은 동일한 주소를 읽는 것과 동일한 레지스터를 참조하지 않습니다. 따라서 읽지 않을 때 "읽고있는 내용"문제가 없습니다. 읽을 때 해당 주소에 쓴 출력은 무엇이든 독립적 인 입력을 읽는 것입니다. 유일한 문제는 공용체의 입력면을 읽고 출력면을 쓰는 것입니다. 임베드 된 항목에서 일반적으로 사용되었습니다. 아마도 여전히 그렇습니다.
Steve314

8

C에서는 변형과 같은 것을 구현하는 좋은 방법이었습니다.

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

litlle 메모리의 시간 에이 구조는 모든 멤버가있는 구조체보다 적은 메모리를 사용합니다.

그런데 C가 제공하는 방식

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

비트 값에 액세스합니다.


두 예제가 모두 표준에 완벽하게 정의되어 있지만; 그러나 비트 필드를 사용하는 것은 이식 할 수없는 코드를 찍는 것이 확실합니까?
legends2k

아닙니다. 내가 아는 한 널리 지원됩니다.
Totonga

1
컴파일러 지원은 휴대용으로 변환되지 않습니다. C Book : C (따라서 C ++) 는 기계어 내에서 필드의 순서를 보장하지 않으므로 후자의 이유로 필드를 사용하면 프로그램을 이식 할 수 없으며 컴파일러에 따라 달라집니다.
legends2k

5

이것은 엄격하게 정의되지 않은 동작이지만 실제로는 거의 모든 컴파일러에서 작동합니다. 이와 같은 경우에 자존심있는 컴파일러가 "올바른 일"을 수행해야한다는 것은 널리 사용되는 패러다임입니다. 타입 컴파일러보다 선호되는 것이 확실합니다. 일부 컴파일러에서는 코드가 잘릴 수 있습니다.


2
엔디안 문제가 없습니까? "정의되지 않은"과 비교할 때 비교적 쉬운 수정이지만 일부 프로젝트에는 고려할 가치가 있습니다.
Steve314

5

C ++에서 부스트 변형 는 정의되지 않은 동작을 최대한 방지하도록 설계된 안전한 버전의 공용체를 구현합니다.

성능은 enum + union구문 과 동일 하지만 스택도 할당 됨) enum: 대신 형식의 템플릿 목록을 사용합니다. :)


5

동작은 정의되지 않았지만 "표준"이 없음을 의미합니다. 모든 괜찮은 컴파일러는 #pragmas를 제공합니다 패킹 및 정렬을 제어하기 위해 를 하지만 기본값이 다를 수 있습니다. 사용되는 최적화 설정에 따라 기본값도 변경됩니다.

또한, 노동 조합이없는 단지 공간을 절약합니다. 현대적인 컴파일러가 유형 punning을 도울 수 있습니다. 당신이 경우 reinterpret_cast<>모든 컴파일러는 당신이 무엇을하고 있는지에 대한 가정을 만들 수 없습니다. 유형에 대해 알고있는 것을 버리고 다시 시작해야 할 수도 있습니다 (메모리에 쓰기를 강제로 적용하면 요즘 CPU 클럭 속도와 비교할 때 매우 비효율적입니다).


4

기술적으로는 정의되어 있지 않지만 실제로 대부분의 컴파일러는 reinterpret_cast유형을 다른 유형 으로 사용하는 것과 정확히 동일하게 취급 하며 그 결과 구현이 정의됩니다. 현재 코드에서 잠을 잃지 않을 것입니다.


" 한 유형에서 다른 유형으로의 reinterpret_cast는 결과적으로 구현이 정의됩니다. "아닙니다. 구현은 그것을 정의 할 필요가 없으며, 대부분 그것을 정의하지는 않습니다. 또한 임의의 값을 포인터로 캐스팅하는 허용되는 구현 정의 동작은 무엇입니까?
curiousguy

4

실제 공용체 사용에 대한 또 다른 예를 위해 CORBA 프레임 워크는 태그가 지정된 공용체 접근 방식을 사용하여 개체를 직렬화합니다. 모든 사용자 정의 클래스는 하나의 (거대한) 공용체의 멤버이며 정수 식별자 는 demarshaller에게 공용체를 해석하는 방법을 알려줍니다.


4

다른 사람들은 아키텍처 차이 (작은-빅 엔디안)를 언급했습니다.

변수에 대한 메모리가 공유되어 하나에 쓰면 다른 변수가 변경되고 유형에 따라 값이 의미가 없다는 문제를 읽었습니다.

예. union {float f; int i; } x;

xi에 쓰는 것은 xf에서 읽으면 의미가 없습니다 .float의 부호, 지수 또는 가수 구성 요소를 보려는 의도가 아니라면 xf에서 읽습니다.

정렬 문제도 있다고 생각합니다. 일부 변수를 단어로 정렬 해야하는 경우 예상 결과를 얻지 못할 수 있습니다.

예. 노동 조합 {char c [4]; int i; } x;

만약 어떤 기계에서 문자가 워드로 정렬되어야한다면 c [0]과 c [1]은 i와 스토리지를 공유하지만 c [2]와 c [3]은 공유하지 않을 것입니다.


워드로 정렬해야하는 바이트? 말이되지 않습니다. 바이트는 정의에 의해 정렬에 대한 요구 사항이 없습니다.
curiousguy

예, 아마도 더 좋은 예를 사용했을 것입니다. 감사.
Philcolbourn

@ curiousguy : 바이트 배열을 단어로 정렬하려는 경우가 많이 있습니다. 예를 들어 1024 바이트의 많은 배열을 갖고 자주 서로를 복사하려는 경우, 많은 시스템에서 단어를 정렬하면 memcpy()하나에서 다른 속도로 두 배의 속도를 낼 수 있습니다 . 일부 시스템은 그와 다른 이유로 구조 / 연합 외부에서 발생하는char[] 할당 추측 적으로 정렬 할 수 있습니다 . 현존하는 예에서, i요소에 대해 모두 겹칠 것이라는 가정은 c[]이식 불가능하지만, 보장 할 수 없기 때문입니다 sizeof(int)==4.
supercat

4

1974 년에 문서화 된 C 언어에서 모든 구조 멤버는 공통 네임 스페이스를 공유했으며 "ptr-> member"의 의미가 정의되었습니다. "ptr"에 멤버의 변위를 추가하고 멤버 유형을 사용하여 결과 주소에 액세스하는 되었습니다. 이 설계는 다른 구조 정의에서 가져온 멤버 이름과 동일한 오프셋을 사용하지만 동일한 오프셋을 사용할 수있게했습니다. 프로그래머는이 기능을 다양한 목적으로 사용했습니다.

구조체 멤버에 고유 한 네임 스페이스가 할당되면 동일한 변위로 두 구조체 멤버를 선언하는 것이 불가능 해졌습니다. 언어에 공용체를 추가하면 이전 버전의 언어에서 사용되었던 것과 동일한 의미를 달성 할 수있었습니다 (하지만 이름을 둘러싸는 컨텍스트로 내보낼 수없는 경우 여전히 foo-> 멤버를 대체하기 위해 찾기 / 바꾸기를 사용해야 할 수도 있음) foo-> type1.member로). 중요한 점은 노조를 추가 한 사람들이 특정 목표 사용법을 염두에 두는 것이 아니라 초기 의미를 의지 한 프로그래머가 어떤 목적 으로도 여전히 달성 할 수 있는 수단을 제공한다는 점입니다 . 동일한 구문을 사용하기 위해 다른 구문을 사용해야하더라도 동일한 의미론.


그러나 K & R의 책은 단지 "표준"이었던 과거의 C 시대의 경우되지 않은, 정의되지 않은 같은 표준은 이러한 정의로, 역사의 교훈을 감사합니다, 하나를 사용하지 않을에해야하는 어떤 목적 및 UB 땅에 들어갑니다.
legends2k

2
@ legends2k : 표준이 작성 될 때 C 구현의 대부분은 같은 방식으로 노조를 처리했으며 그러한 처리는 유용했습니다. 그러나 일부는 그렇지 않았으며 표준 작성자는 기존 구현을 "부적합"으로 브랜드화하는 것을 싫어했습니다. 대신에, 구현 자들이 ( 이미 수행하고 있다는 사실에 의해 입증 된 바와 같이) 무언가를하도록 표준이 필요하지 않은 경우, 표준을 지정하지 않거나 정의하지 않은 상태로 두면 상태 유지 가 될 것이라고 생각했습니다 . 표준이 작성되기 이전보다 사물을 덜 정의해야한다는 개념은 다음과 같습니다.
supercat

2
훨씬 최근의 혁신으로 보입니다. 이 모든 점에서 특히 슬픈 점은 고급 응용 프로그램을 대상으로하는 컴파일러 작성자가 1990 년대에 구현 된 대부분의 컴파일러에 유용한 최적화 지시문을 추가하는 방법을 알아 내야한다는 것입니다. "구현의 90 %, 결과는 하이퍼 모던 C보다 더 좋고 안정적으로 수행 할 수있는 언어가 될 것입니다.
supercat

2

두 가지 주요 이유로 유니온을 사용할 수 있습니다 .

  1. 예와 같이 다른 방식으로 동일한 데이터에 액세스하는 편리한 방법
  2. 하나만 사용할 수있는 다른 데이터 멤버가있을 때 공간을 절약하는 방법

1 타겟 시스템의 메모리 아키텍처가 어떻게 작동하는지 아는 것에 기초하여 코드를 바로 작성하는 C 스타일의 해킹에 가깝습니다. 이미 말했듯이 실제로 다른 플랫폼을 많이 목표로하지 않으면 정상적으로 벗어날 수 있습니다. 일부 컴파일러에서 패킹 지시문을 사용할 수도 있다고 생각합니다 (구조체에서 수행한다는 것을 알고 있습니다).

COM에 광범위하게 사용되는 VARIANT 형식 에서 2의 좋은 예를 찾을 수 있습니다 .


2

다른 사람들이 언급했듯이 열거 형과 결합되고 구조체로 래핑 된 유니온을 사용하여 태그가 지정된 유니온을 구현할 수 있습니다. 한 가지 실용적인 용도는 Rust를 구현하는 것입니다. Rust Result<T, E>는 원래 순수한 것을 사용하여 구현됩니다 enum(추가 데이터는 열거 형 변형으로 저장할 수 있습니다). 다음은 C ++ 예제입니다.

template <typename T, typename E> struct Result {
    public:
    enum class Success : uint8_t { Ok, Err };
    Result(T val) {
        m_success = Success::Ok;
        m_value.ok = val;
    }
    Result(E val) {
        m_success = Success::Err;
        m_value.err = val;
    }
    inline bool operator==(const Result& other) {
        return other.m_success == this->m_success;
    }
    inline bool operator!=(const Result& other) {
        return other.m_success != this->m_success;
    }
    inline T expect(const char* errorMsg) {
        if (m_success == Success::Err) throw errorMsg;
        else return m_value.ok;
    }
    inline bool is_ok() {
        return m_success == Success::Ok;
    }
    inline bool is_err() {
        return m_success == Success::Err;
    }
    inline const T* ok() {
        if (is_ok()) return m_value.ok;
        else return nullptr;
    }
    inline const T* err() {
        if (is_err()) return m_value.err;
        else return nullptr;
    }

    // Other methods from https://doc.rust-lang.org/std/result/enum.Result.html

    private:
    Success m_success;
    union _val_t { T ok; E err; } m_value;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.