공용체
공용체 (Union)
같은 메모리 공간을 여러 용도로 번갈아 쓰고 싶을 때가 있죠. union은 멤버들의 저장 공간이 서로 겹치는 타입이에요. struct가 멤버들을 순서대로 나란히 배치하는 것과 달리, union은 어느 한 시점에 최대 한 멤버의 값만 저장할 수 있어요. 메모리를 아껴 쓰는 저수준 선택지가 필요할 때 유용해요.
출처: cppreference
본문
union의 타입 지정자는 키워드만 다를 뿐 struct 타입 지정자와 같아요.
문법
union attr-spec-seq(optional) name(optional) { struct-declaration-list } |
(1) |
union attr-spec-seq(optional) name |
(2) |
| 자리 | 의미 |
|---|---|
| name | 정의하려는 공용체의 이름 |
| struct-declaration-list | 임의 개수의 변수 선언, 비트 필드 선언, static assert 선언. 불완전 타입의 멤버나 함수 타입의 멤버는 허용되지 않아요. |
| attr-spec-seq | (C23) 공용체 타입에 적용되는 속성들의 선택적 목록. (2) 형태에서 뒤에 ;가 따라오지 않는(즉 전방 선언이 아닌) 경우엔 허용되지 않아요. |
동작 방식
union은 가장 큰 멤버를 담기에 필요한 크기만큼만 커져요(이름 없는 꼬리 패딩이 추가로 붙을 수도 있어요). 나머지 멤버들은 그 가장 큰 멤버의 일부로서 같은 바이트에 할당돼요.
union에 대한 포인터는 각 멤버에 대한 포인터로 캐스팅할 수 있어요 (union에 비트 필드 멤버가 있다면, 그 비트 필드의 기반 타입 포인터로 캐스팅할 수 있어요). 마찬가지로 union의 아무 멤버에 대한 포인터도 그것을 감싸는 union에 대한 포인터로 캐스팅할 수 있어요.
union 내용물에 접근하기 위해 쓴 멤버가 마지막에 값을 저장할 때 쓴 멤버와 같지 않다면, 저장된 값의 **객체 표현(object representation)**이 새 타입의 객체 표현으로 재해석돼요. 이를 **타입 퍼닝(type punning)**이라고 해요. 새 타입의 크기가 마지막에 쓴 타입의 크기보다 크면, 초과 바이트의 내용은 불특정(unspecified)이에요(트랩 표현일 수도 있어요). C99 TC3(DR 283) 이전에는 이 동작이 정의되지 않았지만, 대부분 그렇게 구현됐어요. (C99부터)
struct와 비슷하게, 이름이 없는 union 타입의 이름 없는 멤버를 **익명 공용체(anonymous union)**라고 해요. 익명 공용체의 모든 멤버는 자신의 union 레이아웃을 유지한 채 둘러싼 struct나 union의 멤버로 간주돼요. 둘러싼 struct나 union도 익명이면 이 규칙이 재귀적으로 적용돼요.
struct v
{
union // anonymous union
{
struct { int i, j; }; // anonymous structure
struct { long k, l; } w;
};
int m;
} v1;
v1.i = 2; // valid
v1.k = 3; // invalid: inner structure is not anonymous
v1.w.k = 5; // valid
struct와 마찬가지로, 이름 있는 멤버(익명 중첩 struct/union으로 얻은 것 포함)가 하나도 없는 union을 정의하면 프로그램의 동작은 정의되지 않아요. (C11부터)
키워드
union
참고
struct와 union의 초기화 규칙은 struct 초기화 문서를 보세요.
예제
여기서는 union의 크기와 멤버 재해석, 그리고 정렬 때문에 패딩이 붙는 모습을 확인해 볼게요.
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
union S
{
uint32_t u32;
uint16_t u16[2];
uint8_t u8;
} s = {0x12345678}; // s.u32 is now the active member
printf("Union S has size %zu and holds %x\n", sizeof s, s.u32);
s.u16[0] = 0x0011; // s.u16 is now the active member
// reading from s.u32 or from s.u8 reinterprets the object representation
// printf("s.u8 is now %x\n", s.u8); // unspecified, typically 11 or 00
// printf("s.u32 is now %x\n", s.u32); // unspecified, typically 12340011 or 00115678
// pointers to all members of a union compare equal to themselves and the union
assert((uint8_t*)&s == &s.u8);
// this union has 3 bytes of trailing padding
union pad
{
char c[5]; // occupies 5 bytes
float f; // occupies 4 bytes, imposes alignment 4
} p = { .f = 1.23 }; // the size is 8 to satisfy float's alignment
printf("size of union of char[5] and float is %zu\n", sizeof p);
}
출력:
Union S has size 4 and holds 12345678
size of union of char[5] and float is 8
더 알아보기
- union과 유사한 타입인
struct의 선언과 초기화 규칙을 함께 보면 차이가 또렷해져요. - 같은 메모리를 여러 타입으로 재해석하는 타입 퍼닝은 정의되지 않은 동작에 주의해야 해요.
- C++의
union선언과 비교해 보세요.