본문 바로가기

고급C,C++

[고급C언어] void형 포인터 - void * voidp


① void형 포인터란?

널 포인터는 아무것도 가리키지 않은 포인터였다. void형 포인터도 같은 의미로 착각하면 안된다. void형 포인터는 어느 것이든 가리킬 수 있는 포인터이다. 좀더 늘여 쓴다면 "void형 포인터는 현재 가리키고 있는 대상체가 정해져 있지 않은 포인터"가 된다.

void *imsip;

imsip는 void형 포인터 변수이다. imsip는 가리키는 대상체가 정해져 있지 않은 포인터 변수라고 정의하였다. imsip는 포인터 변수이기 때문에 imsip 안에는 분명 주소 값이 저장되어 있을 것이다. 이것은 현재 어떠한 대상체를 가리키고 있는가? 이 말을 다시 풀이하면 "현재 가리키고 있는 주소 값에서 몇 바이트를 읽어올 것인가?"와 같은 질문이 된다. imsip 자체는 가리키는 대상체가 따로 지정되어 있지 않기 때문에 imsip가 가리키고 있는 곳에서 몇 바이트를 읽어올 지 알 수가 없으며 이를 "void형 포인터 변수는 가리키는 대상체가 정해져 있지 않다."라고 표현하는 것이다. void형 포인터 변수가 가리키는 주소에서 몇 바이트를 읽어올 지 결정하는 것은 바로 캐스트 연산자이다.

 

 

② void형 변수 포인터 정의

void *voidp;

변수명 앞에 "void *"만 붙여주면 된다.

 

 

③ void형 포인터 변수의 성질

void형 포인터 변수는 특징한 성질을 가지고 있다.

1. 어떠한 형 변환(캐스트 연산자 사용) 없이도 void형 포인터 변수에 대입이 가능하다.

2. void형 포인터 변수에서 값을 읽을 때는 반드시 캐스트 연산자를 사용해야 한다.

3. '*' 연산자(간접지정 연산자)를 사용할 때는 항상 캐스트 연산자를 사용한다.

4. void형 포인터 변수에 ++, --를 사용할 때에는 항상 캐스트 연산자를 사용한다.

 

 

④ void형 포인터 변수의 사용

성질은 모두 4가지로 설명하였다. 여기서는 예제를 이용하여 성질에 입각한 사용법을 알아보자.

 

1. 어떠한 형 변환(캐스트 연산자 사용) 없이도 void형 포인터 변수에 대입이 가능하다.

 

main()

{

    int imsi_int;

    float imsi_float;

    double imsi_double;

    char imsi_char;

 

    void *imsip;

 

    imsip = &imsi_int;

    imsip = &imsi_float;

    imsip = &imsi_double;

    imsip = &imsi_char;

}

컴파일할 때 에러가 전혀 발생하지 않는다. imsip에 할당되는 것이 주소 값이라면 어떠한 형(type)이든지 상관없다.

 

main()

{

    int *imsi_int;

    float *imsi_float;

    double *imsi_double;

    char *imsi_char;

 

    void *imsip;

 

    imsip = imsi_int;

    imsip = imsi_float;

    imsip = imsi_double;

    imsip = imsi_char;

}

역시 컴파일 에러는 없다. imsip에 저장하려는 것이 주소라면 어떠한 할당문도 모두 허용한다는 것을 확인할 수 있다.

 

 

2. void형 포인터 변수에서 값을 읽을 때는 반드시 캐스트 연산자를 사용해야 한다.

main()

{

    int imsi_int = 5;

    float imsi_float = 5.5;

    double imsi_double = 0.7;

    char imsi_char = 'K';

 

    void *imsip;

 

    imsip = &imsi_int;

    printf("imsi_int %d\n", *(int *)imsip);

 

    imsip = &imsi_float;

    printf("imsi_float %d\n", *(float *)imsip);

 

    imsip = &imsi_double;

    printf("imsi_double %d\n", *(double *)imsip);

 

    imsip = &imsi_char;

    printf("imsi_char %d\n", *(char *)imsip);

}

void형 포인터 변수가 가리키는 주소에서 몇 바이트를 읽어올 지 정해지지 않은 상태이므로 이를 해결하기 위해 캐스트 연산자를 이용하고 있다.

 

 

3. '*' 연산자(간접지정 연산자)를 사용할 때는 항상 캐스트 연산자를 사용한다.

int imsi_int = 5;

 

void *imsip;

 

imsip = &imsi_int;

void형 포인터를 위와 같이 정의하고 5가 저장된 주소를 imsip에 할당하였다. imsip에 저장된 주소를 참조하여 값을 하나 취하고 싶다면 어떻게 하면 될까? 이 물음은 imsip가 가리키고 있는 주소 값으로 가서 과연 몇 바이트를 읽어 오느냐에 달려 있다. imsip가 가리키는 곳에서 몇 바이트를 읽어올지는 형(type)이 결정하는데 위의 프로그램으로는 알 수 없다. imsip = &imsi_int;로 알 수 있지 않을까 생각하는가? imsi_int의 주소를 단지 imsip에 저장하는 것 외의 다른 정보는 주지 않기 때문에 컴파일러는 알지 못한다. 쉽게 말해서 컴파일러는 인공지능이 아니다. 그러므로 *imsip라고 사용하면 imsip가 가리키는 곳에서 4바이트를 읽어 이를 사용자에게 알리지 못한다.

 

main()

{

    int imsi_int = 5;

    void *imsip;

 

    imsip = &imsi_int;

 

    printf("%d\n", *imsip);

}

 

[출력결과]

warning : defreferencing 'void *' pointer

              invalid use of void expression

거의 "나보고 어쩌란 말이야?" 수준이다. 그렇다면 컴파일러에게 "가리키는 주소로 가서 몇 바이트를 읽어 오라" 고만 알려주면 된다. 이것을 가능하게 하는 것이 캐스트 연산자이다. 위의 printf()문을 이렇게 고쳐보자.

 

printf("%d\n", *(int *)imsip);

imsip의 값을 int형으로 주소화하고 이를 참조하여 하나의 값을 취한 후 그 값을 출력하고 있다.

 

imsip가 가리키고 있는 곳의 값을 다음과 같이 얼마든지 꺼내어 사용할 수 있다.

imsi_int = *(int *)imsip +10;

 

 

4. void형 포인터 변수에 ++, --를 사용할 때에는 항상 캐스트 연산자를 사용한다.

1, 2, 3번과 마찬가지로 4번 역시 조금만 생각해보면 당연한 정의다. ++나 --를 사용하려면 형(type)을 알아야 그 형(type) 만큼의 분기를 할 수가 있는데 모르면 몇 바이트씩을 분기해야 하는지 컴파일러는 알수가 없기 때문에 문제가 발생한다.

 

 

 

⑤ 주의 사항

void형 포인터에는 주의할 사항이 많지 않다. 다음만 조심한다면 void형 포인터를 사용하는데 무리가 없을 것이다.

main()

{

    void imsi(int *temp);

}

 

void imsi(int temp[])

{

    ...

}

적법한 프로그램이다.

 

main()

{

    void imsi(void *temp);

}

 

void imsi(void temp[])

{

    ...

}

void형 포인터는 첨자 연산을 할 수 없기 때문에 위의 문장은 허락되지 않는다. 첨자 연산을 하려면 역시 캐스트 연산자가 필요한데, imsi()의 본체에서 이를 위한 어떠한 행위도 하고 있지 않기 때문에 위법이 되는 것이다.

 

 

 

 

 

출처: 다시 체계적으로 배우는 C언어 포인터(정보문화사:정재은 저)