본문 바로가기

IT/C++ Programming

C Generic 함수구현 :: void 포인터와 함수 포인터 이야기

/* 
written by kaspy (kaspyx@gmail.com)
*/ 

이번 포스팅에서는 c언어의 Generic 함수 구현을 통해서 void 포인터와 함수 포인터가 왜사용되며 어떻게 사용되는지에 대해 다루어 보겠습니다.

설명하면서 다룬 예제코드는 첨부파일에서 참고


0. 데이터의 교환

아래와 같이 두개의 데이터형이 있다면

이를 서로 교환하기위해서는 

  1. int a = 1, b =2 ;
  2. int t;
  3.  
  4. = a;
  5. = b;
  6. = t;

와 같은 형태로 데이터를 바꿀수있으며 이를 함수로 표현하자면 

  1. void swap(int *a,int *b)
  2. {
  3.         int t ;
  4.         t = *a;
  5.         *= *b;
  6.         *= t;
  7. }
  8.  
  9. a=2;
  10. b=3;
  11. swap(&a,&b);

와같이 구현하여 swap 함수를 구현할수있다. 

그런데 여기서 a와 b가 int 형인데 double 형이거나 float 등등의 데이터도 받아서 Generic 하게 처리하고싶다면 (void 포인터 없이) char 포인터를 사용하여 구현할수 있다. 

  1. //Generic swap
  2. void swap(char *a, char *b, int width)
  3. {
  4.         char t;
  5.         int i;
  6.         for (i=0; i < width; i++, a++, b++)
  7.         {      
  8.         t = *a;
  9.         *= *b;
  10.         *= t;
  11.         }
  12. }
  13.  
  14. int a =1,b=2;
  15. double da = 1.0, db = 2.0;
  16. //int 데이터를 교환
  17. swap( (char*)&a, (char*)&b, sizeof(a));
  18. // double 데이터를 교환
  19. swap( (char*)&da, (char*)&db, sizeof(da));

물론 저 swap 함수를 구현하는 커널 라이브러리 개발자는 편했다 

!! 그러나 !! 이를 사용하는 개발자가 불편하다.  

왜냐하면 swap 함수를 사용하는 개발자는 매번 17, 19 라인처럼 char 포인터(char*)로 데이터 형변환을 해줘서 넘겨주지 않으면 

warning 메시지가 출력되기 때문이다.

1. void 포인터


void 포인터는 특별한 타입을 가리킨다고 선언하지않은 포인터로 모든 데이터형을 가리킬수 있다라고 특히위에서 언급한 불편함을 해결하기위해서 사용된다.  


void 포인터... 타입을 가리지 않고 받는다!!!  swap2 함수로 개선해보자


아래는 데이터 타입을 가리지않고 받으며 데이터 교환을 해주는 함수이다.


  1. void swap2(void *a, void *b, int width)
  2. {
  3.         void *t;
  4.         t = malloc(width);
  5.         memcpy(t,a,width);
  6.         memcpy(a,b,width);
  7.         memcpy(b,t,width);
  8.         free(t);
  9. }
  10.  
  11. swap2(&a,&b,sizeof(a));
  12. swap2(&da,&db,sizeof(da));


교환 함수를 응용하는 알고리즘이라면 바로 정렬이다.


아래와 같은 간단한 버블소트 정렬함수를 구현해보자 (오름차순)


  1. void bubble(int *a, int n)
  2. {
  3.         int i,j;
  4.         for ( i =0; i < n-1; i++){
  5.                 for ( j =0; j < n-1-i; j++)
  6.                 {
  7.                         if ( a[j] > a[j+1]) {
  8.                         swap2((char*)a+j,(char*)a+j+1, sizeof(a[0]));
  9.                         }
  10.                 }
  11.         }
  12. }


그렇다면 정렬에 대해서도 Generic 하게 할수있을까?? 데이터 타입에 구애받지않고 정렬하는 함수를 만들고싶다..


  1. void bubble2(void *a, int n,int width)
  2. {
  3.         int i,j;
  4.         char *= (char*)a;
  5.  
  6.         for ( i =0; i < n-1; i++){
  7.                 for ( j =0; j < n-1-i; j++)
  8.                 {
  9.                         //if ( *(p+j*width) > *(p+(j+1)*width)) //int일때는 제대로 비교가힘듬
  10.                         //if ( *(int*)(p+j*width) > *(int*)(p+(j+1)*width))     //타입에따라 비교방법이 다르다.
  11.                         swap2(p+j*width, p+(j+1)*width, width);
  12.                 }
  13.         }
  14. }


9번라인 비교하는 부분에서 제대로된 비교가 어렵다. 비교대상이 double이라면?? 문자열이라면???_???


이를 해결하기위한 방법으로 함수 포인터가 나온다.


2. 함수 포인터


함수 포인터는 말그대로 함수를 가리키기위한 포인터로 아래와 같이 사용된다.


  1. void foo(void)
  2. {
  3.         printf("foo()\n");
  4. }
  5.  
  6. int main()
  7. {
  8.         //void (void)*p  = foo;   //위치가 틀림
  9.  
  10.         //void (*p)(void) = foo;  //2차원 배열과 같음
  11.  
  12.         //void (*p)(void = (void(*)(void))foo; //이렇게 캐스팅되서 지정됨
  13.  
  14.         void (*p)(void) = &foo; //원래는 이렇게 해야함
  15.        
  16.         p();    //call foo
  17.  
  18.         //foo();        //call foo
  19. }

그럼 앞의 void 포인터의 문제점을 해결하기위한 코드를 짜보자. (cmp함수 추가됨)


  1. int cmp(void *a, void *b)
  2. {
  3.         return *(int *)- *(int*)b;
  4. }
  5.  
  6. void bubble2(void *a, int n, int width, int (*cmp)(void*,void*))
  7. {
  8.         int i,j;
  9.         char *= (char*)a;
  10.         for (i=0;< n-1; i++){
  11.                 for ( j=0; j < n-1-i; j++)
  12.                 {
  13.                         if ( cmp(p+j*width,p+(j+1)*width) >0)
  14.                                 swap2(p+j*width, p+(j+1)*width, width);
  15.                 }
  16.         }
  17. }
  18.  
  19. // 아래와같이 호출
  20. int arr[10] = {3,2,5,8,19,32,11,23,8,1};
  21. bubble2(arr,10,sizeof(int),cmp);


위에서 cmp에 대한 정의는 개발자가 정의해줘야한다. 


실제로 리눅스 커널 코드가 저런식으로 구현되어있고, Generic 한 정렬함수 퀵소트 같은경우 저런식으로 사용되는데 구체적인 예는


https://gsamaras.wordpress.com/code/qsort-c/


을 참고하기 바란다.


'IT > C++ Programming' 카테고리의 다른 글

C99 Flexible array member  (0) 2015.08.04
C언어로 달력 구현하기  (0) 2015.05.18
C언어로 리틀엔디안, 빅엔디안 확인 하기  (5) 2015.03.12