58.4 구조체 포인터 변환하기

자료형 변환을 주로 사용하는 상황은 구조체 포인터를 변환할 때입니다. 이때는 struct와 구조체 이름 뒤에 *을 붙여주고 괄호로 묶어주면 됩니다.

  • (struct 구조체이름 *)포인터
  • ((struct 구조체이름 *)포인터)->멤버

다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요.

type_conversion_struct_pointer.c

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

struct Data {
    char c1;
    int num1;
};

int main()
{
    struct Data *d1 = malloc(sizeof(struct Data));    // 포인터에 구조체 크기만큼 메모리 할당
    void *ptr;    // void 포인터 선언

    d1->c1 = 'a';
    d1->num1 = 10;

    ptr = d1;    // void 포인터에 d1 할당. 포인터 자료형이 달라도 컴파일 경고가 발생하지 않음.

    printf("%c\n", ((struct Data *)ptr)->c1);      // 'a' : 구조체 포인터로 변환하여 멤버에 접근
    printf("%d\n", ((struct Data *)ptr)->num1);    // 10  : 구조체 포인터로 변환하여 멤버에 접근

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

    return 0;
}

실행 결과

a
10

구조체 포인터 d1을 선언하고 구조체 크기만큼 메모리를 할당했습니다. 그리고 각 멤버에 'a'와 10을 저장했습니다.

그 다음에 ptr = d1;과 같이 ptrd1을 할당했습니다. 여기서 ptrvoid 포인터이고 d1Data 구조체 포인터이지만, void 포인터는 포인터 타입을 가리지 않고 다 받아들일 수 있으므로 컴파일 경고가 발생하지 않습니다(암시적으로 자료형 변환이 되기 때문).

이제 ptr로 멤버에 저장된 값을 출력해보겠습니다. 그런데 d1Data 구조체의 포인터라 d1->c1, d1->num1처럼 멤버에 쉽게 접근할 수 있지만, ptrvoid 포인터라 Data 구조체의 형태를 모르는 상태이므로 멤버에 바로 접근을 할 수가 없습니다. 따라서 다음과 같이 ptrData 구조체 포인터로 변환한 뒤 멤버에 접근해야 합니다.

printf("%c\n", ((struct Data *)ptr)->c1);      // 'a' : 구조체 포인터로 변환하여 멤버에 접근
printf("%d\n", ((struct Data *)ptr)->num1);    // 10  : 구조체 포인터로 변환하여 멤버에 접근

만약 (struct Data *)ptr처럼 해주면 ptrData 구조체 포인터로 변환을 하지만 이 상태로는 멤버에 접근할 수 없습니다. 즉, (struct Data *)ptr는 다른 포인터에 메모리 주소를 저장할 때만 사용할 수 있습니다.

(struct Data *)ptr->num1;                // 구조체 멤버에 접근할 수 없음. 컴파일 에러
struct Data *d2 = (struct Data *)ptr;    // 다른 포인터에 메모리 주소를 저장

ptr을 구조체 포인터로 변환한 뒤 멤버에 접근할 때는 자료형 변환과 포인터 전체를 다시 한번 괄호로 묶어줍니다.

그림 58‑4 구조체 포인터 변환과 -> 연산자

이렇게 해야 ptr에서 -> 연산자를 사용하여 구조체 멤버에 접근할 수 있습니다.

참고 | typedef로 구조체 포인터 별칭을 정의하는 방법

typedef를 사용하면 구조체 별칭뿐만 아니라 구조체 포인터의 별칭도 정의할 수 있습니다.

typedef struct 구조체이름 {
    자료형 멤버이름;
} 구조체별칭, *구조체포인터별칭;

다음은 구조체 별칭과 구조체 포인터 별칭을 사용하여 변환하는 방법입니다.

  • (구조체별칭 *)포인터
  • ((구조체별칭 *)포인터)->멤버
  • (구조체포인터별칭)포인터
  • ((구조체포인터별칭)포인터)->멤버

type_conversion_typedef_struct.c

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

typedef struct _Data {
    char c1;
    int num1;
} Data, *PData;     // 구조체 별칭, 구조체 포인터 별칭 정의

int main()
{
    PData d1 = malloc(sizeof(Data));    // 구조체 포인터 별칭으로 포인터 선언
    void *ptr;   // void 포인터 선언

    d1->c1 = 'a';
    d1->num1 = 10;

    ptr = d1;    // void 포인터에 d1 할당. 포인터 자료형이 달라도 컴파일 경고가 발생하지 않음.

    printf("%c\n", ((Data *)ptr)->c1);     // 'a' : 구조체 별칭의 포인터로 변환
    printf("%d\n", ((PData)ptr)->num1);    // 10  : 구조체 포인터 별칭으로 변환

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

    return 0;
}

다음과 같이 typedef로 구조체를 정의하면서 별칭을 만들 때 앞에 *를 붙여주면 구조체 포인터 별칭을 정의할 수 있습니다.

typedef struct _Data {
    char c1;
    int num1;
} Data, *PData;    // 구조체 별칭, 구조체 포인터 별칭 정의
//    ↑ 앞에 *를 붙여서 구조체 별칭 포인터 정의

이때는 구조체 별칭 Data와 구분하고, 포인터라는 것을 명확하게 표시하기 위해 PData처럼 이름 앞에 P를 붙여줍니다.

구조체 포인터 별칭을 사용하면 *를 사용하지 않고 포인터를 선언할 수 있습니다.

PData d1 = malloc(sizeof(Data));     // 구조체 포인터 별칭으로 포인터 선언

ptrData 구조체 포인터로 변환하는 방법도 두 가지가 있습니다. (Data *)ptr와 같이 구조체 별칭 뒤에 *를 붙여서 변환할 수도 있고, (PData)ptr와 같이 구조체 포인터 별칭을 바로 사용하여 변환할 수도 있습니다.

printf("%c\n", ((Data *)ptr)->c1);     // 'a' : 구조체 별칭의 포인터로 변환
printf("%d\n", ((PData)ptr)->num1);    // 10  : 구조체 포인터 별칭으로 변환

지금까지 자료형 변환에 대해 배웠는데 문법이 좀 복잡하고 어려웠습니다. 하지만 자료형 변환은 생각보다 많이 사용되므로 눈에 익혀두는 것이 좋습니다. 특히 C 언어에서는 void 포인터를 다양하게 활용하므로 void 포인터 개념을 이해하는 것이 중요합니다.