13-5.공용체

13-5-가.정의

공용체(Union)는 모든 면에서 구조체와 같으며 선언 문법이나 사용하는 방법이 동일하다. 다만 공용체에 속한 멤버들이 기억 장소를 공유한다는 것만 다르다. 기억 장소를 어떻게 공유한다는 것인지 선뜻 이해하기 힘든데 일단 똑같은 모양의 구조체와 공용체를 선언해 놓고 비교해 보도록 하자. 공용체를 선언할 때는 키워드 struct 대신에 union을 사용한다.

 

struct {

     int a;

     short b[2];

} st;

union {

     int a;

     short b[2];

} un;

 

둘 다 정수형 멤버 a와 크기 2의 short형 배열 b를 멤버로 가지고 있는데 st는 구조체이고 un은 공용체이다. 이 두 변수가 메모리에 구현된 모양을 그림으로 비교해 보면 다음과 같다.

구조체는 선언된 멤버가 순서대로 배치되며 a가 4바이트를 차지하고 이어서 b[0]가 2바이트, b[1]이 2바이트를 각각 차지한다. 구조체의 크기는 모든 멤버 크기의 총합과 같으므로 sizeof(st)는 8이 될 것이다. 공용체는 모든 멤버가 기억 장소를 공유하며 a가 있는 자리에 b도 같이 존재한다. 공용체의 크기는 가장 큰 멤버의 크기와 같으며 sizeof(un)은 4가 된다.

공용체의 멤버들은 항상 공용체의 선두 번지와 같은 공간에 배치되는데 a의 번지나 b의 번지나 동일하다. 따라서 a에 어떤 값을 대입하면 같은 기억 공간에 존재하는 b의 값도 덩달아 바뀌게 되며 반대로 b를 변경하면 a도 같이 변경된다. 다음 예제로 이런 동작을 확인해 보자.

 

: Union

#include <Turboc.h>

 

union tag_kong {

     int a;

     short b[2];

};

 

void main()

{

     tag_kong un;

     un.a=0x12345678;

     printf("un.a=%x\n",un.a);

     printf("un.b[0]=%x\n",un.b[0]);

     printf("un.b[1]=%x\n",un.b[1]);

 

     un.b[0]=(short)0x9999;

     printf("대입 후의 un.a=%x\n",un.a);

}

 

공용체 un의 a멤버에 0x12345678을 대입한 후 a와 b[0], b[1]의 값을 각각 출력해 보았다. 결과는 다음과 같다.

 

un.a=12345678

un.b[0]=5678

un.b[1]=1234

대입 후의 un.a=12349999

 

a는 대입한 값을 그대로 가지며 b 배열 요소 각각은 a값의 상하위 워드를 나누어 가지고 있다. 인텔 계열의 CPU는 정수값을 거꾸로 저장(역워드 방식이라고 한다)하므로 b[1]이 a의 상위 워드, b[0]이 a의 하위 워드를 가진다. 이 상태에서 un.b[0]=0x9999대입문을 실행하면 a는 0x12349999가 될 것이다. 두 멤버가 같은 기억 장소에 저장되어 있기 때문에 둘 중 하나를 바꾸면 나머지 멤버의 값도 바뀌는 것이 당연하다.

그렇다면 도대체 기억 장소를 공유하도록 하는 이유는 무엇일까? 어차피 한 번에 하나의 멤버만 유효한 값을 가질 수 있는데 말이다. 그 이유는 두 개의 멤버가 같은 공간에 배치되어 있으면 원하는 타입을 선택해서 읽고 쓸 수 있기 때문이다. 이때 공용체에 속한 멤버들은 전혀 상관없는 값이 아니라 논리적으로 유사한 값이어야 한다. 예를 들어 똑같은 값의 다른 표현이라든가 값 자체가 표현하는 목적이 동일해야 공용체가 될 수 있다.

un 공용체의 경우 int형을 쓰고 싶으면 a멤버를 참조하고 short형을 쓰고 싶으면 b멤버를 참조하면 된다. 기록할 때는 int형으로 기록하고 읽을 때는 상하위 워드를 나누어 short형으로 읽을 수도 있으며 반대로 상하위 워드를 각각 따로 변경한 후 읽을 때는 32비트의 int형으로 읽는 것도 가능하다. 좀 더 개념적으로 이해하기 쉬운 예를 들어 보도록 하자.

 

union tag_ip {

     unsigned long addr;

     unsigned char sub[4];

};

 

이 공용체는 인터넷 주소인 IP값을 저장하는데 IP는 32비트의 부호없는 정수로 되어 있지만 표기할 때는 210.238.128.136과 같이 한 바이트씩 10진수로 쓰는 것이 일반적이다. 표기를 위해서 값을 읽을 때는 sub 배열을 읽는 것이 편리하고 실제 통신을 위해 주소값을 전달할 때는 32비트의 addr멤버를 읽는 것이 효율적이다. 다음은 공용체의 또 다른 활용예를 보자.

 

union tag_unit {

     int mili;

     double inch;

};

tag_unit length;

 

이 공용체는 길이값을 저장하는데 길이를 표현하는 방식 두가지를 동시에 지원한다. 밀리미터 단위로 길이를 지정할 수도 있고 인치 단위를 사용할 수도 있는데 인치는 밀리미터보다 크기 때문에 소수점 이하 두자리까지 지정하기로 한다. 그래서 mili 멤버는 정수형으로 선언했고 inch 멤버는 실수형으로 선언했다. 물론 트윕스나 포인트, 픽셀 등 다른 단위로도 표현하고 싶다면 멤버를 더 늘리면 된다.

밀리값을 저장하고 싶다면 length.mili 멤버에 값을 저장하고 인치로 저장하고 싶다면 length.inch 멤버를 사용한다. 둘 중 하나의 단위로 길이를 표현할 수 있으므로 굳이 두 단위 모두의 값을 가질 필요는 없으며 그래서 각 멤버가 따로 값을 저장하는 구조체가 아닌 공용체로 선언한 것이다.

그렇다면 공용체에 저장된 값이 어떤 멤버를 기준으로 한 것인지는 어떻게 알 수 있을까? 예를 들어 length에 123이라는 값이 저장되어 있다면 이것은 123밀리미터인가 아니면 123인치인가? 좀 이상하게 들리겠지만 어떤 멤버를 기준으로 저장된 값인지 알 수 있는 방법은 따로 제공되지 않는다. 왜냐하면 공용체는 둘 이상의 멤버가 기억 장소를 공유하도록만 할 뿐이지 어떤 멤버를 통해 값을 대입받았는지는 기억하지 않기 때문이다.

공용체에 저장된 값의 의미는 대입할 때 어떤 멤버를 사용했는가에 따라 달라지는데 이를 정확하게 아는 사람은 여기에 값을 대입한 사람뿐이다. 값을 대입할 때 어떤 멤버에 값을 기억시킬 것인가를 선택할 수 있으며 이 값을 사용할 때는 어떤 의미인지 알아서 사용해야 한다. 필요하다면 공용체가 어떤 타입의 값을 저장하고 있는지를 기억하는 별도의 변수를 둘 수도 있다.

공용체도 선언하면서 초기화를 할 수 있는데 단 첫 번째 멤버에 대해서만 초기값을 줄 수 있다. 다음 선언문은 length 공용체를 선언하면서 mili에 15를 대입한다.

 

tag_unit length={15};

 

만약 inch 단위로 초기값을 주고 싶다면 멤버의 순서를 바꾼 후 실수 초기값을 주거나 아니면 지정하고 싶은 인치값을 밀리미터 단위로 바꾸어 mili에 초기값을 주는 방법밖에 없다.