Home » 레퍼런스 » C » c언어 void 포인터에 대해 알아봅시다.

c언어 void 포인터에 대해 알아봅시다.

c언어의 함수 원형을 보다 보면 심심찮게 void *를 볼 수 있습니다. void 포인터라고 이야기 하는데요. 어떤 type이던 참조할 수 있습니다. 역으로, 어떤 type을 가리키는지 모르기 때문에, 카멜레온이라고 부르기도 합니다. 언제 쓰는지, 왜 필요한지 이 글에서 알아보도록 하겠습니다.


c언어 void 포인터

먼저, c언어에서 포인터는, 주소값을 저장하고 있습니다. 그런데, 여기에도 타입이 있는데요. 예를 들어 int형을 가리키면 int형을 가리키는 포인터라고 부릅니다. 그런데, 어떤 형을 가리키는지 모르는 포인터도 있어요. 이를 void pointer라고 해요.

이것은 단독으로 쓰일 일은 잘 없습니다. 어떻게 쓰이는지 보도록 할게요.

[그림 1] 예제 1번 프로그램

예제 1번 프로그램을 볼게요. 먼저, t가 arr의 시작 주소를 가리키고 있어요. 그리고, q가 있는데요. void형 pointer입니다. t의 주소만 들고 있어요. 여기까지 caller가 하는 역할입니다.

다음, callee는, void형 포인터를 적절한 type으로 형변환 해서 처리합니다. int형을 가리키는 포인터 r이 있어요. 그런데, 여기에 q가 가리키는 주소를 집어넣었는데요. 중요한 것은, int를 가리키는 포인터로 변환 하였다는 것입니다. 다음, r이 가리키는 값을 출력합니다.

[그림 2] 예제 1번의 결과

1번 프로그램의 결과를 봅시다. 그러면 r이 가리키고 있는 값인 0이 출력되었음을 볼 수 있습니다. void형 포인터는 가리키는 type을 모릅니다. 그래서, increment 등을 허용하지 않습니다. 그렇기 때문에, 사용하는 쪽에서 적절히 형변환을 해서 처리해야 하는 것입니다.

그림으로 상황을 정리해 봅시다. 먼저, int형 포인터를 void형 포인터로 형변환 해서, 넘겨주었습니다. 예제 1번 프로그램에서는 caller 주석 부분입니다.

다음, callee, 호출당한 쪽에서, void형 포인터를 적절히 타입 변환을 합니다. 그리고, 적절한 처리를 한 후에, 함수를 끝내게 됩니다. 이런 구조가 거의 대부분이라고 할 수 있습니다.


두 원소 비교하기

먼저, 두 원소를 비교하는 함수를 생각해 보겠습니다. 두 원소의 type은 int가 될 수도 있고, long long형이 될 수도 있어요. 그러면 어떻게 정의해야 할까요?

  • 두 원소는 어떤 type인지 몰라요. 그렇기 때문에
    • caller에서 두 원소를 가리키는 void 포인터를 넘기도록 합니다.
    • 처리하는 쪽인 callee에서, void 포인터를 적절히 형변환 합니다.

즉, void pointer 2개를 인자로 받으면 됩니다. 아래 예제를 볼게요.

[그림 3] 예제 2번 프로그램

예제 2번을 봅시다. 먼저, caller가 a의 주소와 b의 주소, 그리고, a의 주소와 c의 주소를 넘겨주었습니다. 받는 쪽은, void pointer를 받는데요. 우리는 실제로 int형 2개를 비교하는 것입니다. 따라서, int형 포인터로 형변환을 합니다. 그 다음, 두 원소를 비교해서, 결과를 출력합니다.

[그림 4] 2번 프로그램 실행 결과

실행 결과는 위와 같습니다. 왜 이렇게 받을까요? 그냥 int형 포인터를 인자로 받으면 안 될까요? qsort 함수가 4번째 인자로, const void형 포인터를 인자로 2개 받고, int형을 리턴하는 함수 포인터를 받습니다. 그냥 int형 pointer로 받으면 안 될까요? 생각해 보면, 우리는 int형만을 정렬하지 않습니다.

  • 어떨 때에는 struct 도 정렬합니다.
  • 어떨 때에는 long long 도 정렬합니다.

이 상황에서, 4번째 compare 함수를 int형 pointer 2개를 받게끔 한다면, long long type이나, struct를 정렬할 때 처리하지 못합니다. 그러므로, 별도의 함수를 생성해야 합니다. 하지만, void 포인터를 2개를 받는 함수포인터로 선언하면 이야기가 달라집니다.

qsort 함수의 layer를 봅시다. 먼저, qsort 함수가 있을 겁니다. 그리고 키 기반 정렬 함수의 특성상, 키 2개를 비교하는 compare 함수가 있을 겁니다. 이 compare 함수를 qsort 내부에서 호출하면, 우리가 정의한 함수가 호출됩니다.

우리가 정의한 compare 함수에서, void 포인터를 강제 형변환 해서 값을 얻어와서 처리합니다. 만약에, int 자료형이 들어있는 배열을 정렬한다고 했으면, 위와 같은 layer가 그려집니다.

long long형은 어떤가요? qsort 부분은 바뀐 게 없습니다. 바뀐 것은, 우리가 정의한 compare 함수에서, long long형을 가리키는 포인터로 형변환 했다는 것입니다. 결국, 중요한 것은 qsort 내부를 바꾸거나, 다른 타입의 정렬 함수를 만들 필요가 없었다는 것입니다.

단지 바뀐 것은, 우리가 정의한 비교 함수였을 뿐입니다. c언어 void 포인터를 사용해서, 더 유연해 질 수 있다가 포인트입니다.


메모리 복사하기

memcpy, memmove 등은 특정 공간에 있는 내용을, 다른 곳으로 복사합니다. 그런데, 이 함수들도, c언어 void 포인터 2개 받습니다. 어떤 형의 포인터라도 받을 수 있게 하기 위해서입니다.

[그림 5] 예제 3번 프로그램 선언부

my_memcpy는 3개의 인자를 받습니다. 앞의 2개는 복사될 공간, 원본 데이터입니다. 마지막은 몇 byte나 복사될 것인지를 의미해요.

[그림 6] main 함수

main 함수는 복잡하지 않아요. 그냥, brr에 값들을 채우고, arr에 복사합니다. 그리고, arr에 있는 내용을 출력합니다. 이제 my_memcpy를 구현해 봅시다. 우리는 뭘 알고 있나요?

  • 복사 위치, 원본 위치
  • 몇 byte만큼 복사할 것인지

그렇기 때문에, byte단위로 가져와서, 반복하면 됩니다.

byte 단위로 가져오기 때문에, 어떤 type이 저장되어있는지 몰라도 됩니다. 그냥 byte 단위로 무식하게 복사하면 되기 때문입니다. char type이 1byte만큼 차지한다면, 받는 쪽에서, char형으로 받아, 처리하면 됩니다.

이를 그림으로 보면 위와 같습니다. 그리고, 말한 내용을 그대로 코드로 옮기면 아래와 같습니다.

[그림 7] my_memcpy 함수

char형 포인터로 강제 형변환 한 이유는, 제 환경에서 char형이 1byte이기 때문입니다. 이 트릭은, qsort 함수에서도 쓰입니다. 특히 원소를 바꿀 때 비슷하게 하겠지요? 그러니, 잘 봐두시면 좋습니다.

Leave a Comment

4 × 4 =