10-2-나.void형 포인터의 활용

포인터로 액세스해야 할 대상체가 분명히 정해져 있을 때는 해당 대상체형의 포인터 변수를 사용하면 된다. 예를 들어 정수형의 ar 배열을 액세스할 때는 정수형 포인터를 사용하고 문자열을 다루고 싶을 때는 문자형 포인터를 쓴다. 그러나 모든 상황에서 대상체를 미리 결정할 수 있는 것은 아니며 임의의 대상체에 대해 동작해야 할 경우가 있다. 대표적으로 메모리를 특정한 값으로 채우는 memset 함수를 보자.

 

void *memset(void *s, int c, size_t n);

 

이 함수는 s번지에서 n바이트만큼 c값으로 가득 채우는데 주로 배열 전체를 0으로 초기화할 때 사용된다. 실제 사용예를 보자.

 

int ari[10];

char arc[20];

double ard[30];

 

memset(ari,0,sizeof(ari));

memset(arc,0,sizeof(arc));

memset(ard,0,sizeof(ard));

 

이 함수의 첫 번째 인수 s가 void형 포인터로 되어 있기 때문에 정수형 배열, 문자형 배열, 실수형 배열을 구분하지 않고 모두 인수로 받아들일 수 있다. 함수를 호출할 때 실인수 값이 형식 인수로 대입되는데 형식인수가 void *형이므로 호출문에 캐스트 연산자를 쓸 필요 없이 배열 이름만 적으면 배열의 시작 번지를 나타내는 포인터 상수가 형식 인수로 전달될 것이다. memset 함수의 원형은 "시작 번지하고 길이만 던져, 몽땅 원하는 값으로 채워 주마"라는 것을 설명하고 있다.

만약 void형 포인터가 없다면 각각의 타입에 대해 memsetint, memsetchar, memsetdouble 같은 함수를 따로따로 만들어야 하므로 무척 불편할 것이다. memset 함수가 임의의 타입에 대해 메모리 채우기를 하기 위해서는 임의의 대상체에 대한 포인터를 모두 전달받을 수 있어야 하며 이럴 때 사용하는 것이 바로 void *형이다.

다음 예제는 void *형을 사용하는 전형적인 예이다. 이 예제의 arDump 함수는 배열의 내용을 바이트 단위로 덤프하는데 타입과 상관없이 임의의 배열에 대해 동작하기 위해 void *형을 형식 인수로 사용한다.

 

: voidDump

#include <Turboc.h>

void arDump(void *array, int length);

 

void main()

{

     int ari[]={1,2,3,4,5};

     char arc[]="Pointer";

 

     arDump(ari,sizeof(ari));

     arDump(arc,sizeof(arc));

}

 

void arDump(void *array, int length)

{

     int i;

     for (i=0;i<length;i++) {

          printf("%02X ",*((unsigned char *)array+i));

     }

     printf("\n");

}

 

arDump 함수로 배열의 시작 번지와 길이만을 전달하면 이 배열의 모든 내용이 16진수로 덤프된다. 함수 내부에서 배열의 끝을 알 수는 없기 때문에 길이도 인수로 전달해야 한다. 실행 결과는 다음과 같다.

 

01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00

50 6F 69 6E 74 65 72 00

 

정수형 배열 ari든 문자형 배열 arc든 모두 array 형식 인수가 대입받는다. array는 void *형이므로 전달받은 실인수의 타입에 대해서는 알지 못하며 오로지 그 시작 번지만을 알고 있다. 함수 내부에서 array 번지의 값을 읽으려면 캐스팅을 해야 하는데 바이트 단위로 출력할 것이므로 unsigned char *형으로 캐스팅했다.

일단 캐스팅을 하면 +i 연산문도 사용할 수 있고 *연산자로 이 위치의 값을 읽는 것도 가능해진다. array는 대상체의 시작 번지만을 전달받으며 대상체의 타입에 대해서는 모르지만 함수 내부에서 원하는 형식대로 캐스팅해서 사용할 수 있다. 만약 arDump 함수를 수정하여 바이트 단위가 아닌 워드 단위(16비트)로 배열 내용을 출력하고 싶다면 다음과 같이 수정한다.

 

for (i=0;i<length/2;i++) {

     printf("%04X ",*((unsigned short *)array+i));

}

 

캐스트 연산자를 unsigned short *로 바꾸면 +i 연산문에 의해 2바이트 단위로 이동할 것이고 *연산자는 이 위치에서 16비트를 읽을 것이다. 바이트 단위가 워드 단위로 바뀌면 length는 절반으로 줄어 들어야 한다.

참고로 임의의 타입을 가리키는 void라는 키워드는 클래식 C에는 없었다. 그래서 클래식 C에서는 타입없이 메모리의 한 지점을 가리키는 용도로 1바이트 단위로 이동되는 char *를 대신 사용했었다. 그러나 이렇게 되면 char *형에 다른 타입의 포인터를 대입할 때마다 캐스트 연산자가 필요해서 무척 불편하며 char *가 정말 char형 변수를 가리키는 것과 임의의 타입을 가리키는 것이 구분되지 않아 실수의 가능성도 무척 높다. 그래서 ANSI C표준에서 void라는 타입을 추가했으며 임의 타입의 포인터가 필요할 때 void *를 쓰기로 한 것이다.