47. 공용체(Union)
1. 키워드
- 공용체(Union)
- 엔디언(Endianness)
- 공용체 별칭
- 익명 공용체
- 공용체 포인터
2. 공용체 사용하기
- 이번에는 공용체라는 특별한 자료형을 만들어보자.
- 공용체는 구조체와 정의 방법이 같지만 멤버를 저장하는 방식이 다르다.
- 즉, 다음과 같이 구조체는 멤버들이 각각 공간을 차지하지만 공용체는 모든 멤버가 공간을 공유한다.
- 공용체는 멤버 중에서 가장 큰 자료형의 공간을 공유한다.
3. 공용체를 만들고 사용하기
- 공용체는
union
키워드를 사용하여 정의한다.
- 공용체는 정의만 해서는 사용을 할 수가 없다.
- 따라서 공용체도 변수로 선언해서 사용한다.
#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strcpy 함수가 선언된 헤더 파일
union Box // 공용체 정의
{
short candy; // 2바이트
float snack; // 4바이트
char doll[8]; // 8바이트
};
int main()
{
union Box b1; // 공용체 변수 선언
printf("%d\n", sizeof(b1)); // 공용체의 전체 크기는 가장 큰 자료형의 크기
stpcpy(b1.doll, "bear"); // doll에 문자열 "bear" 복사
printf("%d\n", b1.candy);
printf("%f\n", b1.snack);
printf("%s\n", b1.doll);
return 0;
}
// 8
// 25954
// 4464428256607938511036928229376.000000
// bear
- 먼저
union
키워드 뒤에 공용체 이름을 지정해 주고{}
(중괄호) 안에 변수를 선언해 준다. - 그리고 공용체를 정의할 때
}
(닫는 중괄호) 뒤에는 반드시;
(세미콜론)을 붙여준다.
- 공용체는 보통
main
함수 바깥에 정의한다. - 만약 함수 안에서 공용체를 정의하면 해당 함수 안에서만 공용체를 사용할 수 있다.
- 정의한 공용체를 사용하려면 공용체 변수를 선언해야 한다.
- 이때는 공용체 이름 앞에 반드시
union
키워드를 붙여준다. - 다음은
Box
공용체 타입의 변수b1
을 선언한다는 뜻이다.
- 이제
sizeof
로 공용체의 크기를 구해 보자.
- Box 공용체의 멤버
short candy
는 2바이트,float snack
은 4바이트,char doll[8]
은 8바이트이다. - 따라서 가장 큰 자료형이
char
타입8
개 크기의 배열이므로 공용체의 전체 크기는8
이다.
- 이제 공용체의 멤버에 접근해 보자.
- 다음과 같이 일반 변수로 선언한 공용체의 멤버에 접근할 때는
.
(점)을 사용한다. - 여기서는
b1.doll
에 문자열"bear"
를 복사했다.
stpcpy(b1.doll, "bear"); // doll에 문자열 "bear" 복사
printf("%d\n", b1.candy);
printf("%f\n", b1.snack);
printf("%s\n", b1.doll);
printf
로b1.candy
,b1.snack
,b1.doll
의 값을 출력해 보면b1.doll
은"bear"
가 정상적으로 출력되지만b1.candy
,b1.snack
의 값은 엉망이다.- 구조체와는 달리 공용체는 멤버 중에서 가장 큰 자료형의 공간을 공유한다.
- 따라서 어느 한 멤버에 값을 저장하면 나머지 멤버의 값은 사용할 수 없는 상태가 된다.
- 그래서 공용체의 멤버는 한 번에 하나씩 쓰면 값을 정상적으로 사용할 수 있다.
b1.candy = 10;
printf("%d\n", b1.candy); // 10: 사용(출력)
b1.snack = 60000.0f;
printf("%f\n", b1.snack); // 60000.00000000: 사용(출력)
strcpy(b1.doll, "bear");
printf("%s\n", b1.doll); // bear: 사용(출력)
- 만약
b1.candy
,b1.snack
,b1.doll
을 구조체로 만들었다면 구조체의 전체 크기는 14바이트이다. - 이때 기본값이 4바이트로 구조체 멤버 정렬이 되면 16바이트다.
- 공용체로 멤버를 한 번에 하나씩만 쓰는 상황이라면 크기는 8바이트이므로 6바이트 이득이다.
- 공용체는 여러 멤버에 동시에 접근하지 않는 경우, 같은 메모리 레이아웃에 멤버를 모아둘 때 사용한다.
- 특히 공용체는 임베디드 시스템이나 커널 모드 디바이스 드라이버 등에서 주로 사용하며 보통은 거의 쓰지 않는다.
4. 공용체와 엔디언
- 공용체 멤버에 값을 저장하고 가져오는 방법을 좀 더 자세히 알아보자.
#include <stdio.h>
union Data // 공용체 정의
{
char c1;
short num1;
int num2;
};
int main()
{
union Data d1; // 공용체 변수 선언
d1.num2 = 0x12345678; // 리틀 엔디언에서는 메모리에 저장될 때 78 56 34 12로 저장됨
printf("0x%x\n", d1.num2); // 4바이트 전체 값 출력
printf("0x%x\n", d1.num1); // 앞의 2바이트 값만 출력
printf("0x%x\n", d1.c1); // 앞의 1바이트 값만 출력
printf("%d\n", sizeof(d1)); // 공용체의 전체 크기는 가장 큰 자료형의 크기
return 0;
}
// 0x12345678
// 0x5678
// 0x78
// 4
- 공용체 변수
d1
의 멤버 중에서 가장 큰 자료형인num2
에0x12345678
을 할당했다.
printf
로d1.num2
,d1.num1
,d1.c1
을 출력해 보면d1.num2
는 저장한 숫자가 그대로 나오지만 다른 멤버는 숫자의 일부분만 나온다.- 공용체는 값을 저장하는 공간은 공유하지만 값을 가져올 때는 해당 자료형의 크기만큼 가져오기 때문이다.
d1.num1
은 2바이트 크기의short
타입이므로 앞의 2바이트인0x5678
만 나온다.- 마찬가지로
d1.c1
은 1바이트 크기의char
타입이므로 앞의 1바이트인0x78
만 나오게 된다.
- 우리가 사용하는 x86(x86-64) 계열 CPU는 리틀 엔디언이라는 방법으로 값을 메모리에 저장한다.
- 리틀 엔디언은 숫자를 1바이트씩 쪼개서 낮은 자릿수가 앞에 온다.
0x12345678
을 리틀 엔디언 방식으로 메모리에 저장하면78 56 34 12
가 된다.- 공용체는 앞에서부터 자료형의 크기만큼 값을 가져오게 되므로
d1.num1
은 앞의 2바이트56 78
을 가져오게 되고,d1.c1
은 앞의 1바이트78
만 가져오게 되는 것이다. - 저장할 때 뒤집혀서 저장되었으므로 가져올 때는 다시 되돌려서 가져온다.
- 따라서
78 56
이 아니라56 78
이 된다.
typedef
와 익명 공용체
- 공용체도 구조체와 마찬가지로
typedef
로 별칭을 지정하고, 익명 공용체를 정의할 수 있다.
- 먼저 공용체 별칭은 다음과 같이 정의한다.
typedef union _Box // 공용체 이름은 _Box
{
short candy;
float snack;
char doll[8];
} Box; // typedef를 사용하여 공용체 별칭을 Box로 정의
typedef
로 공용체의 별칭을 만들었다면 변수는 다음과 같이 선언한다.
- 익명 공용체는 다음과 같이 정의한다.
- 변수는 공용체 별칭으로 선언하면 된다.
공용체를 정의하는 동시에 변수 선언하기
- 다음과 같이 공용체는 정의하는 동시에 변수를 선언할 수 있다.
5. 공용체 포인터를 선언하고 메모리 할당하기
- 구조체와 마찬가지로 공용체도 포인터를 선언할 수 있으며 공용체 포인터에는
malloc
함수를 사용하여 동적 메모리를 할당할 수 있다.
#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
#include <string.h> // strcpy 함수가 선언된 헤더 파일
union Box // 공용체 정의
{
short candy;
float snack;
char doll[8];
};
int main()
{
union Box *b1 = malloc(sizeof(union Box)); // 공용체 포인터 선언, 메모리 할당
printf("%d\n", sizeof(union Box)); // 공용체의 전체 크기는 가장 큰 자료형의 크기
strcpy(b1->doll, "bear"); // doll에 문자열 "bear" 복사
printf("%d\n", b1->candy);
printf("%f\n", b1->snack);
printf("%s\n", b1->doll);
free(b1); // 동적 메모리 해제
return 0;
}
// 8
// 25954
// 4464428256607938511036928229376.000000
// bear
- 먼저
union Box *b1
과 같이union
키워드와 공용체 이름을 사용하여 공용체 포인터를 선언한다. - 일반 변수가 아닌 포인터 변수이므로 반드시
*
를 붙인다. - 그리고
malloc
함수로 메모리를 할당할 때 크기를 알아야 하므로sizeof(union Box)
와 같이 공용체 크기를 구하여 넣어준다.
- 구조체와 마찬가지로 공용체 포인터도 멤버에 접근할 때는
->
(화살표 연산자)를 사용한다.
strcpy(b1->doll, "bear"); // doll에 문자열 "bear" 복사
printf("%d\n", b1->candy);
printf("%f\n", b1->snack);
printf("%s\n", b1->doll);
strcpy(b1->doll, "bear");
과 같이 공용체 포인터의 멤버에 접근한 뒤 문자열을 복사하고,b1->doll
과 같이 값을 가져온다.- 또한, 공용체 포인터도 멤버 중에서 가장 큰 자료형의 공간을 공유한다.
- 마지막으로
free(b1);
처럼 할당한 메모리를 해제해 준다.
공용체 별칭과 익명 공용체 포인터에 메모리 할당하기
typedef
로 정의한 공용체 별칭으로도 포인터를 선언하고 메모리를 할당할 수 있다.
typedef union _Box // 공용체 이름은 _Box
{
short candy;
float snack;
char doll[8];
} Box; // typedef를 사용하여 공용체 별칭을 Box로 정의
Box *b1 = malloc(sizeof(Box)); // 공용체 포인터 선언, 메모리 할당
- 다음과 같이 공용체 포인터에 메모리를 할당하지 않고, 공용체 변수를 그대로 활용할 수 있다.
union Box
{
short candy;
float snack;
char doll[8];
};
int main()
{
union Box b1; // 공용체 변수 선언
union Box *ptr; // 공용체 포인터 선언
ptr = &b1; // b1의 메모리 주소를 구하여 ptr에 할당
strcpy(ptr->doll, "bear"); // doll에 문자열 "bear" 복사
printf("%d\n", ptr->candy);
printf("%f\n", ptr->snack);
printf("%s\n", ptr->doll);
return 0;
}