Programing

왜 우리는 C 연합이 필요합니까?

lottogame 2020. 4. 24. 08:00
반응형

왜 우리는 C 연합이 필요합니까?


노동 조합은 언제 사용해야합니까? 왜 필요한가요?


정수는 종종 이진 표현의 정수와 부동 소수점 사이를 변환하는 데 사용됩니다.

union
{
  int i;
  float f;
} u;

// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);

이것은 C 표준에 따라 기술적으로 정의되지 않은 동작이지만 (가장 최근에 작성된 필드 만 읽어야 함) 거의 모든 컴파일러에서 잘 정의 된 방식으로 작동합니다.

또한 유니온은 C에 의사 다형성을 구현하는 데 사용됩니다. 구조체에 포함 된 객체의 유형을 나타내는 태그를 제공 한 다음 가능한 유형을 함께 결합합니다.

enum Type { INTS, FLOATS, DOUBLE };
struct S
{
  Type s_type;
  union
  {
    int s_ints[2];
    float s_floats[2];
    double s_double;
  };
};

void do_something(struct S *s)
{
  switch(s->s_type)
  {
    case INTS:  // do something with s->s_ints
      break;

    case FLOATS:  // do something with s->s_floats
      break;

    case DOUBLE:  // do something with s->s_double
      break;
  }
}

이를 통해 크기 struct S는 28이 아닌 12 바이트 만 가능합니다.


유니온은 임베디드 프로그래밍 또는 하드웨어 / 메모리에 직접 액세스해야하는 상황에서 특히 유용합니다. 다음은 간단한 예입니다.

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;

그런 다음 다음과 같이 reg에 액세스 할 수 있습니다.

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;

엔디안 (바이트 순서) 및 프로세서 아키텍처는 물론 중요합니다.

또 다른 유용한 기능은 비트 수정 자입니다.

typedef union
{
    struct {
        unsigned char b1:1;
        unsigned char b2:1;
        unsigned char b3:1;
        unsigned char b4:1;
        unsigned char reserved:4;
    } bits;
    unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;

이 코드를 사용하면 레지스터 / 메모리 주소에서 단일 비트에 직접 액세스 할 수 있습니다.

x = reg.bits.b2;

저수준 시스템 프로그래밍은 합리적인 예입니다.

IIRC, 유니온을 사용하여 하드웨어 레지스터를 구성 요소 비트로 분해했습니다. 따라서 구성 요소 비트에 8 비트 레지스터에 액세스 할 수 있습니다.

(정확한 구문은 잊었지만 ...)이 구조를 통해 제어 레지스터를 control_byte 또는 개별 비트를 통해 액세스 할 수 있습니다. 주어진 엔디안에 대해 비트가 올바른 레지스터 비트에 매핑되도록하는 것이 중요합니다.

typedef union {
    unsigned char control_byte;
    struct {
        unsigned int nibble  : 4;
        unsigned int nmi     : 1;
        unsigned int enabled : 1;
        unsigned int fired   : 1;
        unsigned int control : 1;
    };
} ControlRegister;

나는 객체 지향 상속을 대체하는 것으로 두 개의 라이브러리에서 그것을 보았습니다.

예 :

        Connection
     /       |       \
  Network   USB     VirtualConnection

Connection "클래스"가 위 중 하나가되도록하려면 다음과 같이 작성할 수 있습니다.

struct Connection
{
    int type;
    union
    {
        struct Network network;
        struct USB usb;
        struct Virtual virtual;
    }
};

libinfinity에서의 사용 예 : http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74


통합은 상호 배타적 인 데이터 멤버가 동일한 메모리를 공유 할 수 있도록합니다. 이것은 임베디드 시스템과 같이 메모리가 부족한 경우 매우 중요합니다.

다음 예에서

union {
   int a;
   int b;
   int c;
} myUnion;

이 공용체는 3 개의 개별 int 값이 아닌 단일 int의 공간을 차지합니다. 사용자의 값으로 설정하면 다음의 설정 값 B를 , 그것의 값을 덮어 쓰기 이들이 모두 동일한 메모리 위치를 공유하기 때문이다.


많은 사용법. 그냥 grep union /usr/include/*또는 비슷한 디렉토리에 있습니다. 대부분의 경우에 union래핑되어 있고 struct구조체의 한 멤버는 유니온의 어느 요소에 액세스 할 수 있는지 알려줍니다. man elf실제 구현을위한 체크 아웃 예로들 수 있습니다.

이것이 기본 원칙입니다.

struct _mydata {
    int which_one;
    union _data {
            int a;
            float b;
            char c;
    } foo;
} bar;

switch (bar.which_one)
{
   case INTEGER  :  /* access bar.foo.a;*/ break;
   case FLOATING :  /* access bar.foo.b;*/ break;
   case CHARACTER:  /* access bar.foo.c;*/ break;
}

다음은 내 자신의 코드베이스 (메모리 및 구문이 정확하지 않을 수 있음)의 통합 예입니다. 내가 작성한 인터프리터에 언어 요소를 저장하는 데 사용되었습니다. 예를 들어, 다음 코드는

set a to b times 7.

다음 언어 요소로 구성됩니다.

  • 상징 [세트]
  • 변수 [a]
  • 기호 [to]
  • 변수 [b]
  • 상징 [시간]
  • 상수 [7]
  • 상징[.]

언어 요소는 ' #define'값 으로 정의되었습니다 .

#define ELEM_SYM_SET        0
#define ELEM_SYM_TO         1
#define ELEM_SYM_TIMES      2
#define ELEM_SYM_FULLSTOP   3
#define ELEM_VARIABLE     100
#define ELEM_CONSTANT     101

다음 구조를 사용하여 각 요소를 저장했습니다.

typedef struct {
    int typ;
    union {
        char *str;
        int   val;
    }
} tElem;

그런 다음 각 요소의 크기는 최대 공용체의 크기였습니다 (유형의 경우 4 바이트, 공용체의 경우 4 바이트, 실제 값이지만 실제 크기는 구현에 따라 다름).

"set"요소를 만들려면 다음을 사용하십시오.

tElem e;
e.typ = ELEM_SYM_SET;

"variable [b]"요소를 작성하려면 다음을 사용하십시오.

tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later

"constant [7]"요소를 작성하려면 다음을 사용하십시오.

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;

플로트 ( float flt) 또는 합리적 ( struct ratnl {int num; int denom;}) 및 기타 유형 을 포함하도록 쉽게 확장 할 수 있습니다 .

기본 전제는 점이다 strval그 구조는 메모리 위치에 기초하고, 여기서 도시 된 동일한 메모리 블록에 다른 뷰를 얻는 방법 그래서 그들은 오버랩 실제로 메모리에 인접하지 않은 0x1010및 정수 및 포인터 모두이다 4 바이트:

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+

구조에 있다면 다음과 같습니다.

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+

메모리를 절약하는 것과 같이 다른 방식으로 사용될 수있는 메모리를 더 쉽게 재사용 할 수 있다고 말하고 싶습니다. 예를 들어 짧은 문자열과 숫자를 저장할 수있는 "variant"구조체를 만들고 싶습니다.

struct variant {
    int type;
    double number;
    char *string;
};

32 비트 시스템에서는 각 인스턴스에 대해 최소 96 비트 또는 12 바이트가 사용됩니다 variant.

공용체를 사용하면 크기를 64 비트 또는 8 바이트로 줄일 수 있습니다.

struct variant {
    int type;
    union {
        double number;
        char *string;
    } value;
};

더 다양한 변수 유형 등을 추가하려면 더 많은 비용을 절약 할 수 있습니다. 공백 포인터를 캐스팅하는 비슷한 작업을 수행 할 수 있다는 것은 사실 일 수 있습니다. 안전한. 이러한 절감 효과는 크지 않지만이 구조체의 모든 인스턴스에 사용되는 메모리의 1/3을 절약합니다.


이러한 유형의 유연한 구조가 필요할 때, 다른 크기의 메시지를 보낼 메시지 프로토콜에서 특정 상황을 생각하기는 어렵지만, 더 좋고 프로그래머 친화적 인 대안이있을 수 있습니다.

유니언은 다른 언어의 변형 유형과 비슷합니다. 한 번에 하나만 가질 수 있지만 선언 방법에 따라 int, float 등이 될 수 있습니다.

예를 들면 다음과 같습니다.

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};

MyUnion은 가장 최근에 설정 한 것에 따라 int 또는 float 만 포함합니다 . 그래서 이것을하는 :

MYUNION u;
u.MyInt = 10;

u는 이제 10과 같은 정수를 갖습니다.

u.MyFloat = 1.0;

u는 이제 1.0과 같은 부동 소수점을 보유합니다. 더 이상 int를 보유하지 않습니다. printf ( "MyInt = % d", u.MyInt); 특정 동작을 확신 할 수 없지만 오류가 발생했을 수 있습니다.

공용체의 크기는 가장 큰 필드의 크기 (이 경우 float)에 의해 결정됩니다.


유니온은 하드웨어, 장치 또는 네트워크 프로토콜로 정의 된 구조체를 모델링하거나 많은 수의 객체를 생성하고 공간을 절약하려는 경우에 사용됩니다. 그러나 실제로는 95 %가 필요하지 않으며 디버그하기 쉬운 코드를 고수합니다.


이러한 답변 중 상당수는 한 유형에서 다른 유형으로의 캐스팅을 처리합니다. 나는 동일한 유형의 조합 (예 : 직렬 데이터 스트림을 구문 분석 할 때)을 최대한 활용합니다. 그것들은 프레임 된 패킷 의 파싱 / 구축 이 사소하게됩니다.

typedef union
{
    UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
                               // the entire set of fields (including the payload)

    struct
    {
        UINT8 size;
        UINT8 cmd;
        UINT8 payload[PAYLOAD_SIZE];
        UINT8 crc;
    } fields;

}PACKET_T;

// This should be called every time a new byte of data is ready 
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);

void packet_builder(UINT8* buffer, UINT8 data)
{
    static UINT8 received_bytes = 0;

    // All range checking etc removed for brevity

    buffer[received_bytes] = data;
    received_bytes++;

    // Using the struc only way adds lots of logic that relates "byte 0" to size
    // "byte 1" to cmd, etc...
}

void packet_handler(PACKET_T* packet)
{
    // Process the fields in a readable manner
    if(packet->fields.size > TOO_BIG)
    {
        // handle error...
    }

    if(packet->fields.cmd == CMD_X)
    {
        // do stuff..
    }
}

편집 엔디안 및 구조체 패딩에 대한 의견은 유효하고 큰 우려입니다. 나는이 코드를 거의 전적으로 임베디드 소프트웨어에서 사용했는데, 대부분 파이프의 양쪽 끝을 제어했다.


노조는 훌륭합니다. 내가 본 유니언의 현명한 사용은 이벤트를 정의 할 때 사용하는 것입니다. 예를 들어, 이벤트가 32 비트라고 결정할 수 있습니다.

이제이 32 비트 내에서 이벤트 발신자의 식별자에 대해 처음 8 비트를 지정하고 싶을 수 있습니다. 때로는 이벤트 전체를 처리하고 때로는 분석하여 구성 요소를 비교합니다. 노조는 두 가지 모두를 할 수있는 유연성을 제공합니다.

노조 이벤트
{
  부호없는 long eventCode;
  부호없는 char eventParts [4];
};

무엇에 대해 VARIANT그 COM 인터페이스에 사용되는? 여기에는 "type"필드와 "type"필드에 따라 처리되는 실제 값을 보유하는 공용체가 있습니다.


학교에서는 다음과 같은 조합을 사용했습니다.

typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

>> 및 << 연산자를 사용하는 대신 색상을보다 쉽게 ​​처리하기 위해 사용했습니다. 문자 배열의 다른 색인을 통과해야했습니다.


임베디드 장치를 코딩 할 때 유니온을 사용했습니다. 16 비트 길이의 C int가 있습니다. 그리고 EEPROM에서 읽거나 저장해야 할 때 상위 8 비트와 하위 8 비트를 검색해야합니다. 그래서 나는이 방법을 사용했다 :

union data {
    int data;
    struct {
        unsigned char higher;
        unsigned char lower;
    } parts;
};

코드를 쉽게 읽을 수 있도록 시프트 할 필요가 없습니다.

다른 한편으로, 나는 stl 할당 자에 대해 union을 사용하는 오래된 C ++ stl 코드를 보았습니다. 관심이 있으시면 sgi stl 소스 코드를 읽으십시오 . 여기 한 조각이 있습니다.

union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1];    /* The client sees this.        */
};

  • 다른 레코드 유형을 포함하는 파일.
  • 다른 요청 유형을 포함하는 네트워크 인터페이스.

: 이것 좀보세요 X.25 버퍼 명령 처리를

가능한 많은 X.25 명령 중 하나가 버퍼로 수신되어 가능한 모든 구조의 UNION을 사용하여 제자리에서 처리됩니다.


C의 초기 버전에서 모든 구조 선언은 공통 필드 세트를 공유합니다. 주어진:

struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};

컴파일러는 본질적으로 구조체의 크기 (및 정렬) 테이블과 구조체의 멤버 이름, 유형 및 오프셋 테이블을 생성합니다. 컴파일러는 회원이되는 구조에 속한 어느 추적하지 않았고,이 명 구조 유형과이 (멤버로 일치 오프셋 경우에만 동일한 이름을 가진 멤버 가질 수 있도록 할 qstruct xstruct y). p가 임의의 구조 유형에 대한 포인터 인 경우 p-> q는 포인터 p에 "q"의 오프셋을 추가하고 결과 주소에서 "int"를 가져옵니다.

위의 의미를 감안할 때, 함수가 사용하는 모든 필드가 해당 구조 내의 유용한 필드와 정렬되어 있으면 여러 종류의 구조에서 유용한 조작을 상호 교환 가능하게 수행 할 수있는 함수를 작성할 수있었습니다. 이것은 유용한 기능이었으며, 해당 구조 유형에 대해 구조 액세스에 사용 된 멤버를 유효성 검증하기 위해 C를 변경하면 동일한 주소에 여러 개의 이름 지정된 필드를 포함 할 수있는 구조가없는 경우에는이를 상실하게됩니다. C에 "union"유형을 추가하면 그 차이를 어느 정도 메울 수있었습니다 (IMHO는 물론 그럴 필요도 없었습니다).

그 차이를 메우는 노조의 능력의 핵심은 노조 멤버에 대한 포인터가 그 멤버를 포함하는 모든 노조에 대한 포인터로 변환 될 수 있고, 어떤 노조에 대한 포인터도 모든 멤버에 대한 포인터로 변환 될 수 있다는 사실이었습니다. C89 표준은 명시 적 캐스팅 말하지 않았지만 T*직접을 U*모두 포함하는 모든 조합 형식에 대한 포인터로 캐스팅하는 것과했다 TU에 있음을 캐스팅 한 후, 및 U*, 후자의 캐스팅 순서없이 정의 행동에 의해 영향을받을 것 노동 조합 유형이 사용하고 표준에서 직접 주조에 대한 어떤 반대되는 의미를 지정하지 않았습니다 TU. 또한, 기능이 알려지지 않은 기원의 포인터를 통해 객체를 작성하는 동작을 수신 한 경우 T*변환,T*U*통해 객체를 읽는 것은 type U*멤버를 통해 공용체를 작성하고 type T으로 읽는 것과 동일합니다 U. 이는 몇몇 경우 (예 : 공통 초기 시퀀스 멤버에 액세스 할 때) 표준 구현 (구체적으로 정의 됨)입니다. 나머지에 대해서는 정의되지 않은 것보다). 프로그램이 실제 유니온 유형의 오브젝트로 CIS 보증을 이용하는 것은 드물지만, 원산지가 알려지지 않은 오브젝트에 대한 포인터가 유니온 멤버에 대한 포인터처럼 작동하고 이와 관련된 동작 보증이 있어야한다는 사실을 악용하는 것이 훨씬 흔했습니다.


간단하고 매우 유용한 예는 ...

상상해보십시오.

당신은이 uint32_t array[2]와 바이트 체인의 3, 4 바이트에 액세스하려는. 당신은 할 수 있습니다 *((uint16_t*) &array[1]). 그러나 이것은 슬프게도 엄격한 앨리어싱 규칙을 위반합니다!

그러나 알려진 컴파일러를 사용하면 다음을 수행 할 수 있습니다.

union un
{
    uint16_t array16[4];
    uint32_t array32[2];
}

기술적으로 이것은 여전히 ​​규칙 위반입니다. 그러나 모든 알려진 표준이이 사용법을 지원합니다.

참고 URL : https://stackoverflow.com/questions/252552/why-do-we-need-c-unions

반응형