F.R.I.D.A.Y.

공용체(union) part1. default 본문

DEV/C C++

공용체(union) part1. default

F.R.I.D.A.Y. 2020. 1. 28. 22:36
반응형

 하나의 공간을 여러 용도로 사용하는 경우가 있습니다. C언어에도 그러한 공간을 만들 수 있도록 문법을 제공하고 있는데요. 오늘 시간에는 공용체(union, 유니온) 문법에 대해 알아봅니다.

 

# 구조체를 먼저 배우고 오면 더욱 좋습니다.


공유하는 공간

 회의장이 있다고 생각해봅시다. 이 회의장은 넓어서 행사를 개최할 때도 사용하고 여러 사람들이 모여 아이디어를 펼치는 아이디어룸으로 사용할 수도 있습니다. 어떤 시안에 대해 회의를 할 때도 사용하기도 하죠. 학교에선 다목적 강당으로 생각해도 좋을 것 같습니다.

 이 회의장을 하나의 용도로만 사용해야 한다면 다른 용도를 위한 공간을 계속해서 만들어야 하니 비용이 많이 들 것입니다.


공용체(union) 선언하기

 공용체 문법은 아래와 같이 기본 구성을 가지고 있습니다.

union data{
    int a;
    char b;
    short c;
    float d;
};

 모양이 구조체 문법과 비슷합니다. 선언함에 있어서도 구조체의 선언 방법과 동일합니다. 그러나 구조체 문법과는 차이가 있습니다. 그 차이를 지금부터 설명합니다.

 

 구조체와의 차이

 구조체는 특정 대상의 정보를 하나의 그룹으로 표현하는데 적합한 문법이라 했습니다. 그러다 보니 구조체의 공간은 멤버 변수의 크기 총합과 같거나 큽니다[# 구조체 정렬에 의해 발생하는 것으로 자세한 내용은 다음 포스트 참고.]. 그러나 공용체 문법의 경우에는 다릅니다. 하나의 공간을 여러 변수가 함께 사용하기 때문에 공용체의 크기는 멤버 변수 중에서 가장 큰 크기의 자료형이 공용체의 크기가 됩니다.

union data{
    int a;
    char b;
    short c;
    float d;
};

 위에 선언된 공용체 'data'의 크기는 얼마일까요? 가장 큰 크기인 것은 4Byte의 int(혹은 float)입니다. 따라서 union data의 크기는 4Byte로 고정됩니다. 실제로 맞는지 알아보겠습니다.

#include <stdio.h>

union data{
    int a;
    char b;
    short c;
    float d;
};

int main(void){
	union data t;
	
	printf("%lu\n", sizeof(t));
	
	return 0;
}

결과. union data의 크기가 4이다.

 가장 큰 자료형인 int(혹은 float) 크기를 따라 결정되었습니다. union data가 선언될 때 메모리에는 아래와 같이 공간을 공유하도록 만들어집니다.

가장 좌측이 union data의 시작.

 공간을 공유하다 보니 어느 하나의 멤버를 사용(쓰기)했다면 다른 멤버로 작성한 값이 변동됩니다. 값의 오염이 이루어지는 셈이죠. 이 점은 굉장히 중요한 것으로, 코드로 알아보겠습니다.

더보기

# 샘플 코드

#include <stdio.h>

union age{
    char people;
    short tree;
    int asteroid;
};

int main(void){
    int input;
    union age obj = {0};
    
	while(1){
		printf("어떤 대상의 나이를 입력합니까?\n");
    	printf("1. people 2. tree 3.asteroid etc.exit\n");
    	printf("입력: ");
    	scanf("%d", &input);
    
    	if(input == 1){
	        printf("사람의 나이: ");
	        scanf("%hhd", &obj.people);
	    }else if(input == 2){
	        printf("나무의 수령: ");
	        scanf("%hd", &obj.tree);
	    }else if(input == 3){
	        printf("소행성의 나이: ");
	        scanf("%d", &obj.asteroid);
	    }else{
			break;
		}
	    
	    printf("사람  : %d\n", obj.people);
	    printf("나무  : %d\n", obj.tree);
	    printf("소행성: %d\n", obj.asteroid);
   
	}
	
    return 0;
}

 샘플 코드에서 사람을 선택하고 값을 입력해보겠습니다.

 사람뿐 아니라 나무, 소행성의 나이 역시 15가 출력됨을 알 수 있습니다. 이렇게 같은 메모리 영역을 공유한다는 것을 알 수 있습니다. 나무의 수령으로 150을 입력해보겠습니다.

 나무와 소행성의 나이는 150으로 나왔는데 사람은 -106으로 나왔습니다. 어째서 이 문제가 발생할까요? 원인은 멤버 변수의 크기에 있습니다.

 

 우리는 사람은 char, 나무는 short, 소행성은 int[# 물론 우주적으론 부족한 값이지만 :)]로 설정했습니다. char은 8비트 중 7번 비트[# MSB, 비트 번호를 읽는 법은 이 문서 참고]를 부호 비트로 사용합니다. 150을 이진수로 변환해보겠습니다.

1001 0110

 MSB 영역을 침범하게 됩니다. char로 변환했을 때, 부호 비트가 1이니 음수이고 음수의 최소 값인 -128에 0001 0110[# 22]를 더해줍니다. 그러면 -128 + 22 = -106이 나오게 됩니다.


공용체의 사용

 그럼 이렇게 멤버 변수를 하나밖에 사용할 수 없는데 이런 문법을 어디에 사용하느냐? 궁금해하실 수 있습니다. 물론 일반적인 프로그래밍에서는 거의 사용하지 않습니다.

 

 어떤 변수에 값을 비트 단위로 넣어야하고 추출해야하는 문제가 있습니다. 그러나 우리는 비트 연산자를 잘 모릅니다. 이럴 때 공용체와 구조체를 이용해 편리하게 비트 단위로 값을 추출할 수 있습니다.

union data{
    struct d{
        char _0:1; // LSB
        char _1:1;
        char _2:1;
        char _3:1;
        char _4:1;
        char _5:1;
        char _6:1;
        char _7:1; // MSB
    } bit;
    char origin;
};

 위와 같은 유니온 자료형을 선언하고 이용한다고 가정합니다.

#include <stdio.h>

union data{
    struct d{
        char _0:1; // LSB
        char _1:1;
        char _2:1;
        char _3:1;
        char _4:1;
        char _5:1;
        char _6:1;
        char _7:1; // MSB
    } bit;
    char origin;
};

int main(void){  
    union data t;
    t.origin = 96;
	
    printf("%hhd %hhd %hhd %hhd (-) %hhd %hhd %hhd %hhd\n", 
            (char)t.bit._0, (char)t.bit._1, (char)t.bit._2, (char)t.bit._3, 
            (char)t.bit._4, (char)t.bit._5, (char)t.bit._6, (char)t.bit._7);
    printf("%hhd\n", t.origin);
    return 0;
}

 실제 값을 t의 멤버 변수 origin에 대입하게 되면 공용체의 특성으로 인해 그 내부 변수인 struct d 자료형의 bit 멤버 변수가 t의 멤버 변수 origin이 가진 값을 공유하게 됩니다. 이미지로 보면 다음과 같습니다.

 struct d 자료형의 bit 변수 안에 있는 멤버 변수들이 각 비트에 접근하는 변수인 것입니다.

 

 물론 이 경우에는 구조체의 비트 필드 연산자[# :]를 사용하였으나, 1 Byte 단위로 데이터를 추출하고자 할 때 함수나 전처리 지시자를 이용한 매크로 함수를 작성하지 않고도 접근하는 경우에도 사용할 수 있습니다.

 

 들은 바에 의하면 극한의 메모리 최적화가 필요한 곳[# 임베디드 시스템 정도]에서 종종 사용한다고 합니다. 물론 리눅스 커널 소스코드에서도 심심찮게 살펴볼 수 있습니다.


 알고 있는 문법이지만 수 년간의 프로그래밍 생활동안 유니온 문법은 타인에게 소개하려고 사용한 경우를 빼곤 손에 꼽네요.

 

더 읽어보기

 구조체에서 주로 사용하기 때문에 구조체 파트로 설명했지만, 공용체에서도 사용할 수는 있습니다. 물론 사용하면 의미가 있겠느냐마는요.

 

구조체(struct) part2. 비트 필드

이번 시간에는 구조체에서 사용하는 비트 필드에 대해 알아봅니다. 원래 구조체를 설명하고 곧바로 시작할 생각이었는데 공용체 문법에서 먼저 나오게 되었네요 :| # 비트 필드 연산자는 공용체에서도 사용할 수..

pang2h.tistory.com

 

# index

728x90
반응형

'DEV > C C++' 카테고리의 다른 글

포인터(pointer) part3. 함수 포인터  (0) 2020.01.30
구조체(struct) part2. 비트 필드  (0) 2020.01.29
포인터의 크기  (0) 2020.01.26
포인터 배열? 배열 포인터?  (0) 2020.01.26
다차원 배열의 요소 값 교환하기  (0) 2020.01.25
Comments