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

이번에는 구조체 안에 구조체 멤버에 메모리를 할당해보겠습니다. 먼저 다음은 구조체 안에 구조체 멤버가 변수로 있는 상태에서 메모리를 할당하여 사용하는 방법입니다.

struct_variable_in_struct_alloc_memory.c

#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;
}

실행 결과

82 3045671234

struct Person *p1 = malloc(sizeof(struct Person));과 같이 사람 구조체 포인터에 메모리를 할당합니다. 이때 구조체의 각 멤버에 접근하려면 어떻게 해야 할까요?

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

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

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

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

구조체 포인터 사용 끝났다면 반드시 free 함수로 동적 메모리를 해제합니다.

이번에는 구조체의 포인터를 멤버로 가지는 상황을 알아보겠습니다.

struct_pointer_in_struct_alloc_memory.c

#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;
}

실행 결과

82 3045671234

사람 구조체를 살펴보면 struct Phone *phone;과 같이 구조체 포인터를 멤버로 가지고 있습니다.

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

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

struct Person *p1 = malloc(sizeof(struct Person));    // 바깥 구조체의 포인터에 메모리 할당
p1->phone = malloc(sizeof(struct Phone));             // 멤버 포인터에 메모리 할당
그림 55‑1 구조체 메모리 할당 순서

이제 각 멤버에 접근하려면 어떻게 해야 할까요? p1은 포인터이므로 -> (화살표 연산자)를 사용하면 되고, 구조체 멤버 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);           // 구조체 메모리 해제
그림 55‑2 구조체 메모리 해제 순서

바깥에 있는 구조체 포인터를 먼저 해제하면 데이터가 사라지므로 안에 있는 멤버에 더 이상 접근할 수 없게 됩니다. 그래서 멤버 포인터에 저장된 주소도 알 수 없으므로 해제도 할 수 없습니다. 다음과 같이 멤버 포인터의 주소를 미리 다른 곳에 저장해두었다면 바깥에 있는 구조체 포인터를 먼저 해제해도 됩니다.

struct Phone *phone = p1->phone;

free(p1);       // 바깥의 구조체 메모리를 먼저 해제
free(phone);    // 미리 저장해둔 포인터를 이용해서 멤버 포인터 해제
참고 | 구조체 정의 순서

구조체를 Phone Person 순서가 아니라 PersonPhone 순서로 정의하면 문제가 없을까요? 실제로 순서를 바꿔서 컴파일 해보면 정의되지 않는 구조체를 사용한다는 식으로 에러 메시지가 나옵니다. 구조체 안에 구조체가 포인터로 들어갈 때는 전방 선언(forward declaration)을 사용하면 정의되지 않은 구조체를 먼저 사용할 수 있습니다.

  • struct 구조체이름;

struct_forward_declaration.c

struct Phone;    // Phone 구조체 전방 선언

struct Person {
    char name[20];
    int age;
    struct Phone *phone;    // 아직 정의되지 않은 구조체를 포인터로 선언
};

struct Phone {    // Phone 구조체 정의
    int areacode;
    unsigned long long number;
};

전방 선언은 struct Phone;와 같이 struct 키워드에 구조체 이름을 지정해주면 됩니다. 단, 전방 선언을 했다면 반드시 구조체를 따로 정의해주어야 합니다.