본문 바로가기
카테고리 없음

C Union 알아보기 및 사용

by SuldenLion 2023. 3. 21.
반응형

C의 Union에 대하여 알아보고 사용하도록 해보겠다.

 

Union은 구조체와 생김새가 거의 같다. 하지만 큰 차이점으로는 Union 내의 모든 Data member 들이 하나의 메모리 기억장소를 쓴다는 점이 있다.

 

Union은 다양한 type의 메시지를 주고받는 통신 쪽과 같이 여러 타입의 Data member 들을 사용하거나 할 때 쓰인다.

Union은 Data member 중 가장 크기가 큰 type의 변수 크기로 메모리를 할당한다.

예를 들어, Union에 char, int, double의 세가지 타입의 데이터가 들어있다면 각각 1byte, 4byte, 8byte이다. 그러면 그 중 가장 큰 double의 크기 값 8byte로 char과 int 영역의 크기도 할당된다.

 

Union이 아닌 구조체 내용 기억장소 할당 방식

대충 이런 느낌으로 기억장소를 할당받아 사용하는 것이다.

(오류 수정)

위의 방식은 구조체의 할당 방식이고 Union은 아래 그림이 더 적합하다.

비교를 위해 잘못된 그림도 그냥 두겠다.

 

typedef struct _variant_data { //declaration
	int type;
	union {
		int x;
		double y;
		char z;
	} data;
} VariantData;

void print(VariantData *p) 
{
	switch (p->type) {
		case 1: {
			printf("%d\n", p->data.x);
			break;
		}
		case 2: {
			printf("%f\n", p->data.y);
			break;
		}
		case 3: {
			printf("%c\n", p->data.z);
			break;
		}
	}
}

main() 
{
	VariantData a; // definition
	VariantData b;
	a.type = 1;
	a.data.x = 50;
	print(&a);
	b.type = 2;
	b.data.y = 34.51;
	print(&b);
	a.type = 3;
	a.data.z = 'a';
	print(&a);
}

VariantData 라는 Union 객체를 만들어 주겠다. Union은 세 가지 타입의 변수 int, double, char을 가질 것이다. 메인 함수에선 VariantData의 타입이 1이면 정수를, 2이면 실수를, 3이면 문자를 저장해주며 프린트를 해볼건데, 마찬가지로 print 함수도 각 타입에 따라 알맞는 데이터를 출력시켜준다.

 

일반적인 구조체 메모리 할당 방식
Union 방식. 가장 큰 데이터 멤버의 크기를 공유

이런 식으로 쓰면서 기억장소를 적당히 효율적으로 쓰자는 취지인것 같다.

 

Union은 보통 단일 변수가 Data member로서 쓰이는 경우는 잘 없고, 배열 형태로 있는 경우가 많을 것이다.

typedef struct _variant_data { //declaration
	int type;
	union {
		int x[4];
		double y[4];
		char z[4];
	} data;
} VariantData;

void print(VariantData *p) 
{
	int i;
	switch (p->type) {
		case 1: {
			for (i = 0; i < 10; i++) {
				printf("%d ", p->data.x[i]);
			}
			break;
		}
		case 2: {
			for (i = 0; i < 10; i++) {
				printf("%f ", p->data.y[i]);
			}
			break;
		}
		case 3: {
			for (i = 0; i < 10; i++) {
				printf("%c ", p->data.z[i]);
			}
			break;
		}
		printf("\n");
	}
}

main() 
{
	VariantData a; // definition
	VariantData b;
	a.type = 1;
	a.data.x[0] = 50;
	a.data.x[1] = 60;
	a.data.x[2] = 70;
	a.data.x[3] = 80;
	print(&a);
	b.type = 2;
	b.data.y[0] = 34.51;
	b.data.y[1] = 34.52;
	b.data.y[2] = 34.53;
	b.data.y[3] = 34.54;
	print(&b);
	a.type = 3;
	a.data.z[0] = 'a';
	a.data.z[1] = 'b';
	a.data.z[2] = 'c';
	a.data.z[3] = 'd';
	print(&a);
}

방식은 첫번째 코드와 별반 다를것은 없다.

 

하지만 이제 Union과 똑같은 작업을 하는 코드를 Union 없이 만들어보도록 하겠다.

바로 포인터를 잘 활용하면 된다.

 

void print(int *x)
{
	if (*x == 1) {
		x++;
		printf("%d\n", *x);
	} else if (*x == 2) {
		double *p;
		x++;
		p = (double *)x;
		printf("%f\n", *p);
	} else if (*x == 3) {
		char *c;
		x++;
		c = (char *)x;
		printf("%c\n", *c);
	}
}
main()
{
	int *p;
	double *q;
	char *r;
	p = (int *)malloc(sizeof(int)+sizeof(int));
	*p = 1;
	p[1] = 10;
	print(p);
	p = (int *)malloc(sizeof(int)+sizeof(double));
	*p = 2;
	q = (double *)(p + 1);
	*q = 23.14;
	print(p);
	p = (int *)malloc(sizeof(int)+sizeof(char));
	*p = 3;
	r = (char *)(p + 1);
	*r = 'c';
	print(p);
}

 

이런 포인터 타입의 기억 장소를 malloc으로 정확히 원하는 size만큼 할당 받아 사용할 수 있다. 세가지 포인터 변수는 모두 int 타입의 숫자를 하나 할당할 공간을 준비할 것인데, 위의 코드들과 같이 해당 int가 1이면 정수를, 2면 실수, 3이면 문자가 들어온다는 일종의 key이다. 

 

값을 넣을 때는, int 형은 첫번째 변수 타입을 알려줄 int 기억공간 바로 뒤부터 숫자를 넣어주면 된다.

double 형은 변수타입 기억공간 한칸 뒤지만 (double *)로 type casting을 해주어야 실수를 위한 저장공간이 될 것이다.

char 형도 마찬가지로 int의 한칸 뒤부터 (char *)로 type casting한 기억공간에 값을 담아줄 수 있게 한다.

 

값을 출력해줄 print() 함수를 보겠다.

첫 번째 포인터 값이 1이냐 2냐 3이냐에 따라서 그 다음번지의 주소값들을 type에 따라 각각 처리해준다.

 

#include <stdio.h>
#include <malloc.h>

void print(int *x)
{
	int i;
	if (*x == 1) {
		x++;
		for (i = 0; i < 10; i++) printf("%d ", x[i]);
	} else if (*x == 2) {
		double *p;
		x++;
		p = (double *)x;
		for (i = 0; i < 10; i++) printf("%f ", p[i]);
	} else if (*x == 3) {
		char *c;
		x++;
		c = (char *)x;
		for (i = 0; i < 10; i++) printf("%c ", c[i]);
	}
	printf("\n");
}

main()
{
	int *p;
	double *q;
	char *r;
	int i;

	p = (int *)malloc(sizeof(int)+10*sizeof(int));
	*p = 1;
	for (i = 0; i < 10; i++) {
		p[i+1] = 10+i;
	}
	print(p);

	p = (int *)malloc(sizeof(int)+10*sizeof(double));
	*p = 2;
	q = (double *)(p + 1);
	for (i = 0; i < 10; i++) {
		q[i] = 23.5*i;
	}
	print(p);

	p = (int *)malloc(sizeof(int)+10*sizeof(char));
	*p = 3;
	r = (char *)(p + 1);
	for (i = 0; i < 10; i++) {
		r[i] = 'c'+i;
	}
	print(p);
}

위와 마찬가지 포인터 방식의 기억장소 할당 및 처리를 많은 양의 데이터가 있을 때의 경우이다.

 

(내용 추가)

Union은 이제는 거의 쓰지 않는 방식이 되었는데 이유로 현재는 객체지향 방식으로 프로그래밍을 함에 있다. 상속 제도를 씀으로써 겹치는 타입의 변수들은 부모 클래스에 있게 하는 식으로 하는게 일반적이게 되었기 때문에 Union은 점점 쓰지 않게 됐다.

반응형

댓글