기본 패턴
c
#include <stdio.h>
union Value {
int i;
float f;
char bytes[4];
};
union Value v;
v.i = 0x3F800000;
printf("float: %f\n", v.f); // 1.000000 — 같은 비트를 float로 읽음
printf("byte0: %02X\n", (unsigned char)v.bytes[0]); // 00설명
모든 멤버는 같은 주소를 공유한다
struct는 각 멤버마다 별도의 메모리를 할당하지만, union은 모든 멤버가 같은 시작 주소를 공유합니다. union의 크기는 가장 큰 멤버의 크기입니다. 한 멤버에 값을 쓰면 다른 멤버로 읽을 때 같은 비트를 다른 타입으로 해석합니다.
c
union U {
int i; // 4바이트
double d; // 8바이트
char c; // 1바이트
};
printf("%zu\n", sizeof(union U)); // 8 — 가장 큰 멤버(double) 크기
union U u;
u.i = 42;
// u.d, u.c는 현재 정의되지 않은 값 — 마지막으로 쓴 멤버만 유효
printf("%d\n", u.i); // 42 ✅타입 펀닝 — 같은 비트를 다른 타입으로 해석
union을 이용하면 포인터 캐스트 없이 같은 비트 패턴을 다른 타입으로 읽을 수 있습니다. 네트워크 패킷 파싱이나 부동소수점 비트 조작에 활용됩니다.
c
// float의 비트 패턴을 int로 읽기 — C에서 합법적인 타입 펀닝
union FloatBits {
float f;
uint32_t bits;
};
union FloatBits fb;
fb.f = 1.0f;
printf("bits: %08X\n", fb.bits); // 3F800000 — IEEE 754 표현
// ❌ 포인터 캐스트는 UB (strict aliasing 위반)
// uint32_t bits = *(uint32_t *)&fb.f; // 정의되지 않은 동작
// ✅ union 타입 펀닝은 C99/C11 표준에서 허용태그 공용체 — 타입 안전한 variant
union만 쓰면 현재 어느 멤버가 유효한지 알 수 없습니다. enum으로 활성 멤버를 추적하는 태그 공용체(tagged union) 패턴이 이 문제를 해결합니다.
c
typedef enum { VAL_INT, VAL_FLOAT, VAL_STRING } ValType;
typedef struct {
ValType type; // 어떤 멤버가 유효한지 표시
union {
int i;
float f;
const char *s;
} data;
} Value;
void print_value(const Value *v) {
switch (v->type) {
case VAL_INT: printf("int: %d\n", v->data.i); break;
case VAL_FLOAT: printf("float: %f\n", v->data.f); break;
case VAL_STRING: printf("str: %s\n", v->data.s); break;
}
}
Value a = { .type = VAL_INT, .data.i = 42 };
Value b = { .type = VAL_STRING, .data.s = "hello" };
print_value(&a); // int: 42
print_value(&b); // str: hello빠른 정리
| 상황 | 적합한 선택 |
|---|---|
| 멤버들이 동시에 유효할 때 | struct |
| 같은 메모리를 여러 타입으로 쓸 때 | union |
| 여러 타입 중 하나를 안전하게 담을 때 | enum + union (태그 공용체) |
| float/int 비트 변환 | union 타입 펀닝 (*(T*)&x 대신) |
주의할 점
마지막으로 쓴 멤버 외의 멤버를 읽으면 정의되지 않은 동작입니다 (타입 펀닝 예외 제외).
c
union U { int i; float f; };
union U u;
u.i = 1;
// ❌ i를 쓴 후 f를 읽는 것은 타입 펀닝 — C에서는 허용하지만
// C++에서는 UB. 의도를 명확히 주석으로 남길 것
printf("%f\n", u.f);
// ❌ union 크기를 struct처럼 가정하면 안 됨
union Bad { char c; int i; };
printf("%zu\n", sizeof(union Bad)); // 4 (int 크기), char 1이 아님
// ✅ 태그 공용체로 항상 활성 멤버를 명시참고 링크
1 sources