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;과 같이 ptr에 d1을 할당했습니다. 여기서 ptr은 void 포인터이고 d1은 Data 구조체 포인터이지만, void 포인터는 포인터 타입을 가리지 않고 다 받아들일 수 있으므로 컴파일 경고가 발생하지 않습니다(암시적으로 자료형 변환이 되기 때문).
이제 ptr로 멤버에 저장된 값을 출력해보겠습니다. 그런데 d1은 Data 구조체의 포인터라 d1->c1, d1->num1처럼 멤버에 쉽게 접근할 수 있지만, ptr은 void 포인터라 Data 구조체의 형태를 모르는 상태이므로 멤버에 바로 접근을 할 수가 없습니다. 따라서 다음과 같이 ptr을 Data 구조체 포인터로 변환한 뒤 멤버에 접근해야 합니다.
printf("%c\n", ((struct Data *)ptr)->c1); // 'a' : 구조체 포인터로 변환하여 멤버에 접근 printf("%d\n", ((struct Data *)ptr)->num1); // 10 : 구조체 포인터로 변환하여 멤버에 접근
만약 (struct Data *)ptr처럼 해주면 ptr을 Data 구조체 포인터로 변환을 하지만 이 상태로는 멤버에 접근할 수 없습니다. 즉, (struct Data *)ptr는 다른 포인터에 메모리 주소를 저장할 때만 사용할 수 있습니다.
(struct Data *)ptr->num1; // 구조체 멤버에 접근할 수 없음. 컴파일 에러 struct Data *d2 = (struct Data *)ptr; // 다른 포인터에 메모리 주소를 저장
ptr을 구조체 포인터로 변환한 뒤 멤버에 접근할 때는 자료형 변환과 포인터 전체를 다시 한번 괄호로 묶어줍니다.
이렇게 해야 ptr에서 -> 연산자를 사용하여 구조체 멤버에 접근할 수 있습니다.
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)); // 구조체 포인터 별칭으로 포인터 선언
ptr을 Data 구조체 포인터로 변환하는 방법도 두 가지가 있습니다. (Data *)ptr와 같이 구조체 별칭 뒤에 *를 붙여서 변환할 수도 있고, (PData)ptr와 같이 구조체 포인터 별칭을 바로 사용하여 변환할 수도 있습니다.
printf("%c\n", ((Data *)ptr)->c1); // 'a' : 구조체 별칭의 포인터로 변환 printf("%d\n", ((PData)ptr)->num1); // 10 : 구조체 포인터 별칭으로 변환
지금까지 자료형 변환에 대해 배웠는데 문법이 좀 복잡하고 어려웠습니다. 하지만 자료형 변환은 생각보다 많이 사용되므로 눈에 익혀두는 것이 좋습니다. 특히 C 언어에서는 void 포인터를 다양하게 활용하므로 void 포인터 개념을 이해하는 것이 중요합니다.