ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 함수 포인터 인수
    Computer Language/C 2007. 8. 24. 16:50
    728x90

    함 수 포인터는 함수를 가리키고 있지만 어쨋거나 변수이기 때문에 함수의 인수로 전달될 수 있다. 함수를 함수의 인수로 전달한다는 것이 조금 이상하게 들리겠지만 이렇게 하면 함수 내부에서 어떤 함수롤 호출할 것인지를 호출측에서 지정할 수 있다. 함수 포인터가 아니라면 이것은 불가능하다. 함수 포인터를 인수로 받아들이는 함수의 예는 아주 많은데 대표적으로 퀵 소트 함수인 qsort 함수으 원형을 보자.


    void qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*));


    이 함수는 base 번지에서 부터 width 폭을 가지는 num개의 값을 일정한 기준에 따라 정렬하는데 내부적으로 퀵 소트 알고리즘을 사용한다. 퀵 소트 알고리즘은 가장 효율적인 정렬 알고리즘으로 알려져 있어서 일반적인 정렬에 자주 사용한다.

    이 함수를 호출하기 위해서는 정렬 대상과 함께 비교 함수를 전달해야 하는데 네 번째 인수 compare 가 비교 함수를 지정하는 함수 포인터이다.

    무 작위가 흩어져 있는 어떤 값을 일정한 기준에 따라 정렬하기 위해서는 순서대로 각 값의 대소를 비교하여 교체하는 과정을 여러 번 거쳐야 한다. qsort함수는 값을 비교하는 순서를 결정하고 비교 겨로가에 따라 값을 교체하는 알고리즘을 내부적으로 처리하되 단, 값을 비교하는 연산은 직접 할 수 없다. 그래서 호출측에서 값을 비교하는 함수를 compare 인수로 제공해야 한다.


    #include <Turboc.h>

    int compare(const void *a, const void *b)
    {
     if(*(int *)a == *(int *)b) return 0;
     if(*(int *)a > *(int *)b) return 1;
     return -1;
    }

    void main()
    {
     int i;
     int ar[]={34,25,27,19,4,127,9,629,18,7,9,165};
     
     qsort(ar, sizeof(ar) / sizeof(ar[0]), sizeof(int), compare);
     for(i=0; i<sizeof(ar) / sizeof(ar[0]); i++)
     {
      printf("%d 번째 = %d\n", i, ar[i]);
     }
    }
    < 부호만 바꿔주면 오름/내림 정렬 가능>


    정 렬 알고리즘대로 정렬하면 되는데 왜 사용자가 비교 함수를 제공해야 하는가 하면 비교 방법의 값의 성질에 따라 천차만별 달라질 수 있기 때문이다. 12보다 26이 크고, 876보다 1564다 크다. 하지만 24와 078은 수치로 비교하면 078이 더 크지만 문자로 비교하면 24가 더 커서 애매하다. 또한 문자의 경우 대소문자를 구분할 것인지, 문자 중간에 있는 밑줄, 쉼표, 공백, 대시 같은 문자들도 비교 대상에 포함되는지, 한글과 영문의 우선순위는 어떻게 할 것인지 오름차순인지 내림차순인지 등 아주 복잡한 문제들이 많다. 여기에 이차 정렬 까지 고려하면 완벽한 일반화는 불가능 하다.

    그래서 qsort 함수는 값을 직접 비교할 수 없으며 호출측으로 두 값을 비교해 달라는 요청을 하기 위해 compare 함수를 부른다. 두 값을 비교하는 방식은 단순한 타입이 아니라 능동적인 동작이기 때문에 함수가 필요하다. 이때 sqort 함수는 비교할 값들을 가리키는 두 개의 포인터를 전달하며 호출측은 두 포인터로부터 값을 대소 관게를 판별하는 함수를 만든 후 이 함수의 주소를 qsort 에게 전달해야 한다. 지금 다루고 있는 주제는 정렬이 아니므로 qsort 함수에 대해서는 차후 상세하기 따로 공부해 보되 함수 포인터를 인수로 사용하면 이런 것도 간으해진다는 것만 이해하도록 하자.

    함수 포인터를 인수로 받아들이는 함수는 qsort 외에도 아주 많다. 윈도우즈 환경에서는 콜백함수라는 이므으로 환경 포인터를 빈번히 사용하는데 윈도우로 전달되는 메시지를 처리하는 WndProc 이라는 함수가 바로 콜백함수(시스템이 호출하는 사용자 정의 함수)이다. 또한 타이머를 설치하는 SetTimer 함수나 윈도우를 열거하는 EnumWindows 같은 함수들이 모두 함수 포인터를 인수로 한다.

    함수 포인터 인수를 활용하는 예제를 만들어 보자. FTP 서버에서 어떤 파일을 다운로드받아 로컬 하드 디스크에 저장하는 함수 FtpDown을 만든다고 하자. 이 함수의 원형은 아마도 다음과 같을 것이다.


    void FtpDown(const char *src, const char *dest, BOOL (*prog)(int, int));


    세 번째 인수에 prog라는 함수 포인터가 추가되었따. 이 함수는 총 용량과 현재 받고 있는 용얄응ㄹ 두 개의 정수 인수로 받아 들이며 최소 여부를 리턴한다. FtpDown 함수를 호출하는 측에서는 Prog 타입의 함수를 만든 후 이 함수에서 다운로드 과정을 보여주되 그래프로 보여주든, 음악으로 들려주든 아니면 동영상으로 보여주든 마음대로 구현할 수 있다. FtpDown 함수는 주기적으로 Prog 함수를 호출할 뿐이다. 또한 사용자의 입력을 체크하여 취소 여부를 리턴값으로 전달하기도 한다.

    FtpDown 함수를 사용하는 가상의 예제는 다음과 같다. 실제로 네트워크 접속은 하지 않으므로 다운로드 되지 않지만 컴파일은 가능하며 함수 포인터가 어떻게 활용되는가를 살펴보기에는 부족하지 않을 것이다.


    #include <Turboc.h>

    void FtpDown(const char *src, const char *dest, BOOL (*prog)(int, int))
    {
     int total, now;
     BOOL UserBreak;

     total = 600; // 실제 src의 크기를 조사해야 한다.
     now = 0;

     for(now=0; now < total; now++)
     {
      // 다운로드 받는다. 한 번에 1M씩 받는다고 치자.
      //DownloadFile(src, dest);
      delay(10);
      // 과정 표시 함수를 불러준다.
      UserBreak = (*prog)(total, now);
      if(UserBreak == TRUE)
      {
       puts("다운로드를 취소했습니다");
       break;
      }
     }
    }

    BOOL Progress(int total, int now)
    {
     // 다운로드 과정을 보여 줌
     printf("총 %d중 %d만큼 받고 있습니다.\n", total, now);

     // 만약 사용자가 중지하라고 했다면
     if(kbhit() && getch() == 27)
     {
      return TRUE;
     }
     else
     {
      return FALSE;
     }
    }

    void main()
    {
     FtpDown("fpt://babo.com/ondal.mpg", "c:/ondal.mpg", Progress);
    }

    호 출 측에서는 FtpDown 함수를 부르기 전에 이 함수로 전달할 BOOL(*)(int, int) 타입의 사용자 정의 함수를 작성해 두어야 한다. 예제에서는 Progress 라는 이름으로 사용자 정의 함수를 작성하고 인수로 전달되는 total, now값을 참조하여 다운로드 과정을 보여주며 리턴값으로 다운로드 취소를 통제할 수 있다. 다운로드 중에 사용자가 Esc만 누르면 다운로드는 즉시 중지된다. 시간이 오래 걸리는 인쇄, 랜더링, 컴파일 등의 작업을 할 때 흔히 이런 기법이 많이 사용된다.

    그 렇다면 FtpDown 함수 안에서 직접 다운로드 경과를 출력하고 취소 요청을 점검하는 함수 포인터를 사용하는 거소가는 어떤 차이점이 있을까? 이 함수를 직접 만들어서 자신이 쓴다면 경과 출력 방법을 언제든지 뜯어 고칠 수 있으므로 이렇게 해도 상관없다. 그러나 제 3자가 쓸 함수라거나 아니면 자신이 여러 프로젝트에 반복적으로 사용해야 한다면 문제가 달라진다. 이런 함수들의 본체는 굉장히 길고 복잡할 수 있는데 제 3자가 잘 알지도 못하는 함수의 본체를 직접 건드리는 것은 무척 어려운 일이며 작성자도 매번 함수를 뜯어 고쳐가면서 쓰기는 번거롭다. 다운로드에 필요한 핵심 코드는 함수에 미리 작성해 놓고 경과 출력 방법 등 달라질 수 있는 부분만 별도의 함수로 작성할 수 있도록 하면, 누구나 이 함수를 쉽게 사용할 수 있고 재사용성도 좋아진다. 또한 소스가 아닌 컴파일된 라이브러리로 함수를 배포할 수도 있다.

    함수 포인터는 다른 포인터와는 달리 ++, -- 등의 연산자를 사용할 수 없으며 정수와 가감 연산도 할수 없다. 함수는 코드 덩어리이며 이 덩어리의 크기는 가변적이고 실행중에 변경할 수도 없기 때문이다.

    'Computer Language > C' 카테고리의 다른 글

    shared_ptr를 사용해보자!!  (0) 2008.12.08
    TR1(Tecnical Report1) 소개글  (0) 2008.12.08
    포인터로 함수 호출하기  (0) 2007.08.24
    함수포인터 타입  (0) 2007.08.24
    [펌] 함수포인터 정의  (0) 2007.08.24

    댓글

Designed by black7375.