프로그래밍 언어/[ C ]

[ C ] 15. 공용체

kim.svadoz 2020. 8. 15. 14:37
반응형

[ 공용체 ]


공용체는 구조체와 정의 방법이 같지만 멤버를 저장하는 방식이 다르다. 즉, 다음과 같이 멤버들이 각각 공간을 차지하지만 공용체는 모든 멤버가 공간을 공유한다.

image-20200814152144065

즉, 공용체는 멤버 중에서 가장 큰 자료형의 공간을 공유한다. 현실에서 예를 들자면 물건이 하나 들어있는 선물상자와 비슷하다. 같은 크기의 상자지만 들어있는 물건의 종류가 다른 것 처럼.

image-20200814152228745

공용체 만들고 사용하기

union 공용체이름{
    자료형 멤버이름;
};

공용체는 정의만 해서 사용할 수 없고 따로 변수로 선언해서 사용해야한다.

#define _CRT_SECURE_NO_WARNINGS        // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#inlcude <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));        // 8: 공용체의 전체 크기는 가장 큰 자료형의 크기
    strcpy(b1.doll, "bear");        // doll에 문자열 bear 복사

    printf("%d\n", b1.candy);        // 25954
    printf("%f\n", b1.snack);        // 4464428256607938511036928229376.000000
    printf("%s\n", b1.doll);        // bear

    return 0;
}
# 실행 결과
8
25954
4464428256607938511036928229376.000000
bear

image-20200814152615281

  • printf로 b1.candy, b1.snack, b1.doll의 값을 출력해보면 b1.doll은 bear가 정상적으로 출력되지만, b1.candyb1.snack은 값이 엉망이 되었다.
  • 구조체와는 달리 공용체는 멤버 중에서 가장 큰 자료형의 공간을 공유한다. 따라서 어느 한 멤버에 값을 저장하면 나머지 멤버의 값은 사용할 수 없는 상태가 된다.
  • 그래서 공용체의 멤버는 한 번에 하나씩 쓰면 값을 정상적으로 사용할 수 있다.
  • 만약 b1.candy/snack/doll을 구조체로 만들었다면 구조체의 전체크기는 2+4+8 = 14바이트이다.
  • 공용체로 멤버를 한 번에 하나씩만 쓰는 상황이라면 크기는 8바이트이므로 6바이트 이득인것이다.
  • 실무에서는 공용체에 값을 저장할 때 어떤 멤버를 사용할 것인지 미리 정해놓고, 꺼낼때도 정해놓은 멤버에서 값을꺼내는 식으로 사용한다. 즉, 선물 상자 바깥에 어떤 물건이 들어있는지 적어놓고 사용하는 식이다.

=> 정리하자면 공용체는 여러 멤버에 동시에 접근하지 않는 경우 같은 메모리 레이아웃에 멤버를 모아둘 때 사용한다. 특히 공용체는 임베디드 시스템이나 커널모드 디바이스 드라이버 등에서 주로 사용하며 보통은 거의 쓰지않는다.

공용체와 엔디언

#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);        // 0x12345678: 4바이트 전체 값 출력
    printf("0x%x\n", d1.num1);        // 0x5678: 앞의 2바이트 값만 출력
    printf("0x%x\n", d1.c1);        // 0x78 앞의 1바이트 값만 출력

    printf("%d\n", sizeof(d1));        // 4 : 공용체의 전체 크기는 가장 큰 자료형의 크기

    return 0;
}
# 실행 결과
0x12345678
0x5678
0x78
4
  • printf로 출력해보면 d1.num2는 저장한 숫자가 그대로 나오지만 다른 멤버는 숫자의 일부분만 나온다.

  • 공용체는 값을 저장하는 공간은 공유하지만 값을 가져올 때는 해당 자료형의 크기만큼 가져오기 때문이다.

  • d1.num은 2바이트 크기의 short이므로 앞의 2바이트인 0x5678만 나온다. 마찬가지로 d1.c1은 1바이트 크기의 char이므로 1바이트인 0x78만 나온다.

    • 그런데 앞의 값만 나와야 한다면 0x12340x12가 나와야 하는데 왜 0x5678, 0x78이 나올까?

    => 우리가 사용하는 x86(x86-64) 계열 CPU는 리틀 엔디언이라는 방법으로 값을 메모리에 저장한다. 간단하게 이야기하면 리틀 엔디어는 숫자를 1바이트씩 쪼개서 낮은 자릿수가 앞에온다. 사람이 보기에는 반대로 뒤집혀있는 것.

    image-20200814153542609

  • 0x12345678리틀엔디언방식으로 메모리에 저장하면 78 56 34 12가 된다. 공용체는 앞에서부터 자료형의 크기만큼 값을 가져오게 되므로 d1.num1은 앞의 2바이트 56 78 을 가져오게 되고, d1.c1은 앞의 1바이트 78만 가져오게 되는 것이다.( 저장할 때 뒤집혀서 저장되었으므로 가져올 때는 다시 되돌려서 가져온다. 따라서 78 56이 아니라 56 78이 된다.)

image-20200814153740463

  • 공용체도 구조체와 마찬가지로 typedef로 별칭을 지정하고 익명 공용체를 정의할 수 있따.
typedef union 공용체이름{
    자료형 멤버이름;
} 공용체 별칭;
----------------------------------------------------------------------------------------------
typedef union _Box{        // 공용체 이름은 _Box
    short candy;
    float snack;
    char doll[8];
} Box;                    // typedef을 사용하여 공용체 별칭을 Box로 정의
-----------------------------------------------------------------------------------------------
typedef union {            // 익명 공용체 정의
    short candy;
    float snack;
    char doll[8];
} Box;                    // typedef을 사용하여 공용체 별칭을 Box로 정이ㅡ

Box b1;                // 공용체 별칭으로 공용체 변수 선언
------------------------------------------------------------------------------------------------
union Box {            // 공용체 정의
    short cnady;
    float snack;
    char doll[8];
} b1;                // 공용체를 정의하는 동시에 변수 b1 선언

공용체 포인터를 선언하고 메모리 할당하기

#define _CRT_SECURE_NO_WARNINGS
#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));        // 8: 공용체 전체 크기는 가장 큰 자료형의 크기

    strcpy(b1->doll, "bear");        // doll에 문자열 bear 복사

    printf("%d\n", b1->candy);    // 25954
    printf("%f\n", b1->snack);    // 4464428256607938511036928229376.000000
    printf("%s\n", b1->doll);     // bear

    free(b1);
    return 0;
}
# 실행 결과
8
25954
4464428256607938511036928229376.000000
bear
  • 구조체와 마찬가지로 공용체 포인터도 멤버에 접근할 때는 ->(화살표 연산자)를 사용한다.
  • 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);    // 25954
    printf("%f\n", ptr->snack);    // 4464428256607938511036928229376.000000
    printf("%s\n", ptr->doll);     // bear

    return 0;
}
반응형