Skip to content

47. 공용체(Union)


1. 키워드

  • 공용체(Union)
  • 엔디언(Endianness)
  • 공용체 별칭
  • 익명 공용체
  • 공용체 포인터


2. 공용체 사용하기

  • 이번에는 공용체라는 특별한 자료형을 만들어보자.
  • 공용체는 구조체와 정의 방법이 같지만 멤버를 저장하는 방식이 다르다.
  • 즉, 다음과 같이 구조체는 멤버들이 각각 공간을 차지하지만 공용체는 모든 멤버가 공간을 공유한다.


001


  • 공용체는 멤버 중에서 가장 큰 자료형의 공간을 공유한다.


3. 공용체를 만들고 사용하기

  • 공용체는 union 키워드를 사용하여 정의한다.


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


  • 공용체는 정의만 해서는 사용을 할 수가 없다.
  • 따라서 공용체도 변수로 선언해서 사용한다.


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 키워드 뒤에 공용체 이름을 지정해 주고 {}(중괄호) 안에 변수를 선언해 준다.
  • 그리고 공용체를 정의할 때 }(닫는 중괄호) 뒤에는 반드시 ;(세미콜론)을 붙여준다.


union Box // 공용체 정의
{
    short candy;  // 2바이트
    float snack;  // 4바이트
    char doll[8]; // 8바이트
};


  • 공용체는 보통 main 함수 바깥에 정의한다.
  • 만약 함수 안에서 공용체를 정의하면 해당 함수 안에서만 공용체를 사용할 수 있다.


  • 정의한 공용체를 사용하려면 공용체 변수를 선언해야 한다.
  • 이때는 공용체 이름 앞에 반드시 union 키워드를 붙여준다.
  • 다음은 Box 공용체 타입의 변수 b1을 선언한다는 뜻이다.


union Box b1; // 공용체 변수 선언


  • 이제 sizeof로 공용체의 크기를 구해 보자.


sizeof(union 공용체)
sizeof(공용체변수)
sizeof 공용체변수


  • Box 공용체의 멤버 short candy는 2바이트, float snack은 4바이트, char doll[8]은 8바이트이다.
  • 따라서 가장 큰 자료형이 char 타입 8개 크기의 배열이므로 공용체의 전체 크기는 8이다.


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


002


  • 이제 공용체의 멤버에 접근해 보자.
  • 다음과 같이 일반 변수로 선언한 공용체의 멤버에 접근할 때는 .(점)을 사용한다.
  • 여기서는 b1.doll에 문자열 "bear"를 복사했다.


stpcpy(b1.doll, "bear"); // doll에 문자열 "bear" 복사

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


  • printfb1.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의 멤버 중에서 가장 큰 자료형인 num20x12345678을 할당했다.


d1.num2 = 0x12345678;


  • printfd1.num2, d1.num1, d1.c1을 출력해 보면 d1.num2는 저장한 숫자가 그대로 나오지만 다른 멤버는 숫자의 일부분만 나온다.
  • 공용체는 값을 저장하는 공간은 공유하지만 값을 가져올 때는 해당 자료형의 크기만큼 가져오기 때문이다.


0x12345678
0x5678
0x78


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


  • 우리가 사용하는 x86(x86-64) 계열 CPU는 리틀 엔디언이라는 방법으로 값을 메모리에 저장한다.
  • 리틀 엔디언은 숫자를 1바이트씩 쪼개서 낮은 자릿수가 앞에 온다.


003


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


004


typedef와 익명 공용체

  • 공용체도 구조체와 마찬가지로 typedef로 별칭을 지정하고, 익명 공용체를 정의할 수 있다.


  • 먼저 공용체 별칭은 다음과 같이 정의한다.


typedef union 공용체이름 {
    자료형 멤버이름;
} 공용체별칭;
typedef union _Box // 공용체 이름은 _Box
{
    short candy;
    float snack;
    char doll[8];
} Box; // typedef를 사용하여 공용체 별칭을 Box로 정의


  • typedef로 공용체의 별칭을 만들었다면 변수는 다음과 같이 선언한다.


공용체별칭 변수이름;


  • 익명 공용체는 다음과 같이 정의한다.


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


  • 변수는 공용체 별칭으로 선언하면 된다.


공용체별칭 변수이름;
typedef union // 익명 공용체 정의
{
    short candy;
    float snack;
    char doll[8];
} Box; // typedef를 사용하여 공용체 별칭을 Box로 정의

Box b1; // 공용체 별칭으로 공용체 변수 선언


공용체를 정의하는 동시에 변수 선언하기

  • 다음과 같이 공용체는 정의하는 동시에 변수를 선언할 수 있다.


union 공용체이름 {
    자료형 멤버이름;
} 변수;
union Box // 공용체 정의
{
    short candy;
    float snack;
    char doll[8];
} b1; // 공용체를 정의하는 동시에 변수 b1 선언


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

  • 구조체와 마찬가지로 공용체도 포인터를 선언할 수 있으며 공용체 포인터에는 malloc 함수를 사용하여 동적 메모리를 할당할 수 있다.


union 공용체이름 *포인터이름 = malloc(sizeof(union 공용체이름));
#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)와 같이 공용체 크기를 구하여 넣어준다.


union Box *b1 = 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로 정의한 공용체 별칭으로도 포인터를 선언하고 메모리를 할당할 수 있다.


공용체별칭 *포인터이름 = malloc(sizeof(공용체별칭));
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;
}

References