profile image

L o a d i n g . . .

Unit 54. 공용체 사용하기

공용체와 구조체

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

 

 

54.1 공용체를 만들고 사용하기

공용체는 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));  // 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

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

 

sizeof로 공용체의 크기를 구하면 다음과 같다.

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

 

printf b1.candy, b1.snack, b1.doll의 값을 출력해보면 b1.doll "bear"가 정상적으로 출력되지만 b1.candy, b1.snack은 값이 엉망이 되었다. 구조체와는 달리 공용체는 멤버 중에서 가장 큰 자료형의 공간을 공유한다. 따라서 어느 한 멤버에 값을 저장하면 나머지 멤버의 값은 사용할 수 없는 상태가 된다.

 

※ 정리 : 공용체의 멤버는 한 번에 하나씩 쓰면 값을 정상적으로 사용할 수 있다. 따라서 공용체는 여러 멤버에 동시에 접근하지 않는 경우 같은 메모리 레이아웃에 멤버를 모아둘 때 사용한다.

 

 

54.2 공용체와 엔디언

#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

공용체 변수 d1의 멤버 중에서 가장 큰 자료형인 num2 0x12345678을 할당했다.

 

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

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

 

그런데 앞의 값만 나와야 한다면 0x1234와 0x12가 나와야 하는데 왜 0x5678, 0x78이 나오는 걸까?

 

x86(x86-64) 계열 CPU는 리틀 엔디언이라는 방법으로 값을 메모리에 저장한다. 즉, 리틀 엔디언은 숫자를 1바이트씩 쪼개서 낮은 자릿수가 앞에 오기 때문이다.

 

 

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

공용체도 포인터를 선언할 수 있으며 공용체 포인터에는 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));    // 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;
}

먼저 union Box *b1과 같이 union 키워드와 공용체 이름을 사용하여 공용체 포인터를 선언한다. 그리고 malloc 함수로 메모리를 공용체의 크기만큼 할당한다.

 

구조체와 마찬가지로 공용체 포인터도 멤버에 접근할 때는 -> (화살표 연산자)를 사용한다.

 

마지막으로 free(b1);처럼 할당한 메모리를 해제해준다.

 

 

 

Unit 55. 구조체와 공용체 활용하기

55.1 구조체 안에서 구조체 멤버 사용하기

다음과 같이 구조체는 구조체를 멤버로 가질 수 있다.

#include <stdio.h>

struct Phone {    // 휴대전화 구조체
    int areacode;                 // 국가번호
    unsigned long long number;    // 휴대전화 번호
};

struct Person {    // 사람 구조체
    char name[20];         // 이름
    int age;               // 나이
    struct Phone phone;    // 휴대전화. 구조체를 멤버로 가짐
};

int main()
{
    struct Person p1;

    p1.phone.areacode = 82;          // 변수.멤버.멤버 순으로 접근하여 값 할당
    p1.phone.number = 3045671234;    // 변수.멤버.멤버 순으로 접근하여 값 할당

    printf("%d %llu\n", p1.phone.areacode, p1.phone.number);    // 82 3045671234

    return 0;
}

구조체를 멤버로 가지려면 다음과 같이 구조체 안에서 구조체 변수를 선언하면 된다.

 

멤버에 접근할 때는 . (점)을 사용하며 가지고 있는 구조체에 계층적으로 접근하면 된다. 다음은 점을 두 번 사용하여 휴대전화 구조체의 areacode number 멤버에 접근하여 값을 할당하고, 값을 가져온다.

  • 변수.멤버.멤버

 

 

55.2 구조체 안의 구조체 멤버에 메모리 할당하기

다음은 구조체 안에 구조체 멤버가 변수로 있는 상태에서 메모리를 할당하여 사용하는 방법이다.

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일

struct Phone {    // 휴대전화 구조체
    int areacode;                 // 국가번호
    unsigned long long number;    // 휴대전화 번호
};

struct Person {    // 사람 구조체
    char name[20];         // 이름
    int age;               // 나이
    struct Phone phone;    // 휴대전화. 구조체를 멤버로 가짐
};

int main()
{
    struct Person *p1 = malloc(sizeof(struct Person));    // 사람 구조체 포인터에 메모리 할당

    p1->phone.areacode = 82;          // 포인터->멤버.멤버 순으로 접근하여 값 할당
    p1->phone.number = 3045671234;    // 포인터->멤버.멤버 순으로 접근하여 값 할당

    printf("%d %llu\n", p1->phone.areacode, p1->phone.number);    // 82 3045671234

    free(p1);    // 동적 메모리 해제

    return 0;
}

p1은 포인터이므로 -> (화살표 연산자)를 사용하여 멤버에 접근한다. 하지만 phone은 포인터가 아닌 일반 변수이므로 . (점)을 사용하여 멤버에 접근한다.

 

점이냐 화살표냐 구분할 때는 현재 구조체가 선언된 상태를 확인하면 된다. 변수면 점, 포인터면 화살표로 기억한다.

 

다음은 구조체의 포인터를 멤버로 가지는 상황이다.

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일

struct Phone {    // 휴대전화 구조체
    int areacode;                 // 국가번호
    unsigned long long number;    // 휴대전화 번호
};

struct Person {    // 사람 구조체
    char name[20];          // 이름
    int age;                // 나이
    struct Phone *phone;    // 휴대전화. 구조체 포인터 선언
};

int main()
{
    struct Person *p1 = malloc(sizeof(struct Person));    // 바깥 구조체의 포인터에 메모리 할당
    p1->phone = malloc(sizeof(struct Phone));             // 멤버 포인터에 메모리 할당

    p1->phone->areacode = 82;          // 포인터->포인터->멤버 순으로 접근하여 값 할당
    p1->phone->number = 3045671234;    // 포인터->포인터->멤버 순으로 접근하여 값 할당

    printf("%d %llu\n", p1->phone->areacode, p1->phone->number);    // 82 3045671234

    free(p1->phone);    // 구조체 멤버의 메모리를 먼저 해제
    free(p1);           // 구조체 메모리 해제

    return 0;
}

구조체 포인터는 선언만 해서는 사용을 할 수가 없다. 일단 바깥 구조체의 포인터에 메모리를 할당한 뒤 멤버로 있는 구조체 포인터에 메모리를 할당한다.

 

이제 각 멤버에 접근해보자. p1은 포인터이므로 -> (화살표 연산자)를 사용하면 되고, 구조체 멤버 phone도 메모리를 할당한 포인터이므로 똑같이 화살표를 사용하면 된다. 즉, 포인터->포인터->멤버 모양이다.

  • 포인터->포인터->멤버

 

구조체 포인터 사용이 끝났으면 메모리를 해제해야 하는데 순서가 중요하다. 반드시 안쪽에 있는 멤버부터 메모리를 해제한다.

복사했습니다!