답변:
정수는 종종 이진 표현의 정수와 부동 소수점 사이를 변환하는 데 사용됩니다.
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.
다음 언어 요소로 구성됩니다.
언어 요소는 ' #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;
각 요소의 크기는 최대 합집합의 크기였습니다 (일반적인 값이지만 실제 크기는 구현에 따라 다름).
"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 ( float flt
) 또는 rationals ( struct ratnl {int num; int denom;}
) 및 기타 유형 을 포함하도록 쉽게 확장 할 수 있습니다 .
기본 전제는 점이다 str
및 val
그 구조는 메모리 위치에 기초하고, 여기서 도시 된 동일한 메모리 블록에 다른 뷰를 얻는 방법 그래서 그들은 오버랩 실제로 메모리에 인접하지 않은 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 | |
+-------+
make sure you free this later
상수 요소에서 주석을 제거 해야합니까 ?
메모리를 절약하는 것과 같이 다른 방식으로 사용될 수있는 메모리를 더 쉽게 재사용 할 수 있다고 말하고 싶습니다. 예를 들어 짧은 문자열과 숫자를 저장할 수있는 "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)에 의해 결정됩니다.
sizeof(int) == sizeof(float)
( == 32
) 보통.
이러한 답변 중 다수는 한 유형에서 다른 유형으로의 캐스팅을 처리합니다. 나는 동일한 유형의 조합 (예 : 직렬 데이터 스트림을 구문 분석 할 때)을 최대한 활용합니다. 그것들은 프레임 된 패킷 의 파싱 / 구축 이 사소하게됩니다.
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..
}
}
편집 엔디안 및 구조체 패딩에 대한 의견은 유효하고 큰 우려입니다. 나는이 코드를 거의 전적으로 임베디드 소프트웨어에서 사용했는데, 대부분 파이프의 양쪽 끝을 제어했다.
임베디드 장치를 코딩 할 때 유니온을 사용했습니다. 16 비트 길이의 C int가 있습니다. 그리고 EEPROM에서 읽거나 저장해야 할 때 상위 8 비트와 하위 8 비트를 검색해야합니다. 그래서 나는이 방법을 사용했다 :
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
코드를 쉽게 읽을 수 있도록 시프트 할 필요가 없습니다.
반면에, 나는 stl 할당 자에 유니온을 사용하는 오래된 C ++ stl 코드를 보았습니다. 관심이 있으시면 sgi stl 소스 코드를 읽으십시오 . 여기 한 조각이 있습니다.
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
주위 에 그룹화가 필요하지 않습니까? 지금은 둘 다 첫 번째 바이트 만 가리켜 야합니다. higher
lower
: 이것 좀보세요 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];};
컴파일러는 본질적으로 구조체의 크기 (및 정렬) 테이블과 구조체의 멤버 이름, 유형 및 오프셋 테이블을 생성합니다. 컴파일러는 회원이되는 구조에 속한 어느 추적하지 않았고,이 명 구조 유형과이 (멤버로 일치 오프셋 경우에만 동일한 이름을 가진 멤버 가질 수 있도록 할 q
의 struct x
과 struct y
). p가 임의의 구조 유형에 대한 포인터 인 경우 p-> q는 포인터 p에 "q"의 오프셋을 추가하고 결과 주소에서 "int"를 가져옵니다.
위의 의미를 감안할 때, 함수가 사용하는 모든 필드가 해당 구조 내의 유용한 필드와 정렬되어 있으면 여러 종류의 구조에 대해 유용한 조작을 상호 교환 가능하게 수행 할 수있는 함수를 작성할 수있었습니다. 이것은 유용한 기능이었으며, 해당 구조 유형에 대해 구조 액세스에 사용 된 멤버를 유효성 검증하기 위해 C를 변경하면 동일한 주소에 여러 개의 이름 지정된 필드를 포함 할 수있는 구조가없는 경우에는이를 상실하게됩니다. C에 "union"유형을 추가하면 그 차이를 어느 정도 메울 수있었습니다 (IMHO는 물론 그럴 필요도 없었습니다).
그 차이를 메우는 노조의 능력의 핵심은 노조 멤버에 대한 포인터가 그 멤버를 포함하는 어떤 노조에 대한 포인터로 변환 될 수 있고, 어떤 노조에 대한 포인터도 모든 멤버에 대한 포인터로 변환 될 수 있다는 사실이었습니다. C89 표준은 명시 적 캐스팅 말하지 않았지만 T*
직접을 U*
모두 포함하는 모든 조합 형식에 대한 포인터로 캐스팅하는 것과했다 T
및 U
에 있음을 캐스팅 한 후, 및 U*
, 후자의 캐스팅 순서없이 정의 행동에 의해 영향을받을 것 노동 조합 유형이 사용하고 표준에서 직접 주조에 대한 어떤 반대되는 의미를 지정하지 않았습니다 T
에 U
. 또한, 기능이 알려지지 않은 기원의 포인터를 통해 객체를 작성하는 동작을 수신 한 경우 T*
변환,T*
를U*
을 통해 객체를 읽습니다. U*
T
읽는 것은 type 멤버를 통해 공용체를 작성하고 type 으로 읽는 것과 동일합니다 U
.이 경우 몇 가지 경우 (예 : 공통 초기 시퀀스 멤버에 액세스하는 경우) 및 구현 정의 (Undefined가 아닌) 나머지). 프로그램이 공용체 유형의 실제 객체로 CIS 보증을 이용하는 것은 드물지만, 원산지가 알려지지 않은 객체에 대한 포인터는 공용체 멤버에 대한 포인터처럼 행동해야하고 관련 행동 보장이 있어야한다는 사실을 악용하는 것이 훨씬 흔했습니다.
foo
가 int
오프셋이 8 인 경우 anyPointer->foo = 1234;
"포인터에서 주소를 가져와 8 바이트로 대체하고 결과 주소에 1234 값의 정수 저장소를 수행합니다. 컴파일러는 anyPointer
식별 여부를 알거나 신경 쓸 필요가 없습니다" foo
멤버들 사이에 나열된 모든 구조 유형
anyPointer
구조체 멤버를 식별 하는지 여부를 알지 못하면 컴파일러는 to have a member with the same name only if the type and offset matched
게시물의 이러한 상태를 어떻게 확인 합니까?
p->foo
의 유형과 오프셋에 의존 하기 때문에 컴파일러는 구조 멤버의 이름 목록을 유지합니다 foo
. 본질적 p->foo
으로의 약어입니다 *(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. 후자의 질문에 관해서는, 컴파일러가 구조체 멤버 정의를 만났을 때, 그 이름을 가진 멤버가 존재하지 않거나 그 이름을 가진 멤버가 같은 타입과 오프셋을 가져야합니다. 일치하지 않는 구조체 멤버 정의가 존재하면 스 쿼크했을 것이라고 생각하지만 오류를 처리하는 방법을 모르겠습니다.
간단하고 매우 유용한 예는 ...
상상해보십시오.
당신은이 uint32_t array[2]
와 바이트 체인의 3, 4 바이트에 액세스하려는. 당신은 할 수 있습니다 *((uint16_t*) &array[1])
. 그러나 이것은 슬프게도 엄격한 앨리어싱 규칙을 위반합니다!
그러나 알려진 컴파일러를 사용하면 다음을 수행 할 수 있습니다.
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
기술적으로 이것은 여전히 규칙 위반입니다. 그러나 모든 알려진 표준이이 사용법을 지원합니다.