드디어 200번째 글입니다. void형 포인터에 대해서 배워봅시다. 보통 우리는 다음과 같이 썼을 거에요.

 

 

 이것은 무엇을 의미할까요? 일단 p에 15를 넣습니다.

 

 p는 메모리에 어딘가에 할당이 되어 있을 겁니다. 그리고 이 p의 주솟값을 a가 들고 있습니다.

 

 

 예를 들어 p의 주소가 0x80이라면 a는 0x80을 들고 있는 것입니다. 다음에 (*a)++이 있는데요. 이는 (*a) = (*a) + 1을 의미합니다. a를 역참조하면 p가 되므로, 결국 p에 1을 증가시키라는 것과 같습니다.

 

 

 최종적으로는 p = 16이 될 거에요. a는 int형 자료형을 가르켰어요. void형 포인터는 이와 다르게, 주솟값을 저장하고 있기는 한데, 가리키는 자료형을 모르는 pointer를 의미합니다.

 

 


 아래 예제를 봅시다.

 

 int형 변수인 p와 double형 변수인 q가 있습니다.

 

 

 6번째 줄에 void형 포인터 a가 선언되었습니다. a에는 p의 주솟값이 들어가는데요. 이 때, a는 p의 주소값을 들고 있어요. 가리키는 대상은 일단 int형입니다.

 

 

 7번째 줄에는 double형 변수인 q의 주소를 들고 있어요. 즉, a는 q를 가리키고 있어요. 그러면, 이 a는 서로 다른 데이터 타입을 가리킬 수 있다는 이야기가 됩니다. 만약에, a를 void형 포인터가 아닌 int를 가리키는 포인터로 선언을 했다고 해 봅시다.

 

 

 그러면, double *를 int *로 assign 할 수 없다는 오류가 뜰 거에요. 그러면, void형 pointer는 모든 data type을 가리킬 수 있는 만능이라고 생각하면 좋을까요? 일단 넵. generic pointer라고도 이야기를 한다는 것 정도 챙기시면 좋겠습니다.

 

 


 그러면, 역참조가 가능할까요? C 표준에서는 이를 허용하지 않습니다. 그렇지만, 실제로 가능한 지는, 컴파일러마다 다를 수 있어요. 인터넷 컴파일러인 wandbox에서는 허용하지 않습니다.

 

 

 이는 void형 포인터가 어떤 자료형을 가리키는지 모르기 때문입니다. int형? double형? 아니면 long long형? char형? 어떤 것을 가리키는 지 알 수 없어요. 따라서, 강제 형변환을 해 주어야 합니다.

 

 

 8번째 줄을 하나 하나 해석해 봅시다. a는 일단 void형 pointer였습니다. (double *)a는, a를 double형 포인터로 강제 형 변환을 했다는 의미입니다. 그러면 이것을 역참조 하면, 실 데이터 q로 접근할 것인데, q가 double형이고, pointer는 double형을 가리키는 것으로 형변환이 되었으니, 제대로 접근할 수 있겠네요.

 

 따라서, 컴파일 오류도 뜨지 않고 2.0000000000 이라는 값이 출력됩니다. malloc 함수의 리턴 값이 void *형인데, 우리는 이를 (int *)이라던지, (char *)형으로 casting을 했어요. 그런 것과 같다고 보시면 되겠습니다.

 

 


 void 형 포인터는, 모든 자료형을 가리킬 수 있고, 역참조를 할 때 적절히 형변환을 하면 된다. 이것을 잘 기억하시면 왜 qsort의 1번째 인자가 void *인지 알 수 있습니다.

 

 

 만약에, void형 포인터를 인자로 받지 않았다고 생각해 봅시다. 그러면, int형을 비교할 때, double형을 비교할 때, 사용자 구조체 형을 정렬할 때, 각각 따로 함수를 만들어야 했을 거에요. 그런데, 우리는 2번째 인자와 3번째 인자가 왜 필요한지 의문이 드실 겁니다. 이는 간단합니다.

 

 

 void형 pointer를 받았습니다. 그러니, qsort 내부의 입장에서는 한 원소마다 몇 byte의 크기를 가지는지, 몇 개나 있는지 모릅니다. 이 둘을 명시를 해 준다면, 다음 원소를 가기 위해서, 주솟값을 size byte만큼 증가시키면 된다는 것을 알 수 있기 때문입니다. 일례로, size가 10이고, a가 현재 원소를 가리켰는데, 다음 원소를 가리키게 하려면 아래와 같이 작성하면 될 거에요.

 

 

 char형의 크기가 1byte라면, 이렇게 작성해도 무난합니다. 그 다음에 compare가 있습니다. 이것은 void형 포인터를 2개 받는 함수의 포인터인데요.

 

 base가 void형 포인터였다. 어느 형을 가리키는 지 몰랐다. 그렇다면 비교체 함수를 호출할 때 Key 2개를 가지고 비교할 거에요. 그런데 그 키가 어떤 형인지 모릅니다.

 

 

 그런가요? 노란색 2개를 비교했을 때, 어떤 형을 가리키는지 모르는 상황입니다. 그러니 void형 포인터로 인자를 넘겨 받을 겁니다. 그러면 사용자가 compare를 구현할 때 어떻게 하면 되나요? compare 함수 내에서 형변환을 하면 됩니다. 이 부분은 사용자가 재정의를 하는 부분입니다.

 단지 qsort는 내가 정의한 compare를 토대로 key 값 비교를 하면서 정렬을 하는 역할만 할 뿐입니다. 즉, 우리는 double형이던, int형이던, struct moo형이던 compare 함수만 어떻게 잘 정의하면 double형도, int형도, moo형도 sort를 할 수 있습니다. 오늘 배운 것의 강력함이라고 할 수 있어요. void형 포인터는 generic이다. 이 한 마디만 정리하셔도 좋을 듯 싶네요.