본문 바로가기

awesome-c Beginner 번역/A tutorial on pointers

<비공식 번역>awesome-c Beginner 번역 : CHAPTER 10 : Pointers to Functions

현재위치



<주의!!>

'A TUTORIAL ON POINTERS AND ARRAYS IN C'의 공식적인 번역이 아니며 수를 받은 것 역시 아닙니다!!




CHAPTER 10: Pointers to Functions(챕터 10: 함수 포인터)


지금까지 우리는 데이터 객체의 포인터를 이야기했습니다. 또한 C는 함수의 포인터도 허용하고 있습니다. 함수 포인터는 다양한 사용성을 가지고 있고 그들 중 일부는 여기에서 토의될 것입니다.

 

현실적인 문제를 고려해봅시다. 여러분은 배열에 저장될 수 있는 어떤 데이터의 묶음을 정렬할 수 있는 함수의 작성을 원합니다. 아마도 string, integer, float, structure의 배열이 될지도 모릅니다. 정렬 알고리즘은 모두 같을 수 있습니다. 예를 들어, 간단한 버블 정렬을 쓰거나 더 복잡한 쉘 이나 퀵 정렬 알고리즘을 쓸 수 있습니다. 그렇지만 우리는 설명을 목적으로 간단한 버블 정렬을 사용할 것입니다.

 

Sedgewick[1]은 배열을 정렬할 때 포인터를 인자로하는 함수를 만들어 버블 정렬을 C코드로 설명했습니다. 만약 우리가 bubble()함수를 호출한다면 정렬 프로그램은 다음의 bubble_1.c에 설명되어 있습니다:

 

/*-------------------- bubble_1.c --------------------*/

/* Program bubble_1.c from PTRTUT10.HTM   6/13/97 */

#include <stdio.h>

int arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(int a[], int N);

int main(void)
{
    int i;
    putchar('\n');
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr,10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

void bubble(int a[], int N)
{
    int i, j, t;
    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (a[j-1] > a[j])
            {
                t = a[j-1];
                a[j-1] = a[j];
                a[j] = t;
            }
        }
    }
}

/*---------------------- end bubble_1.c -----------------------*/


버블 정렬은 간단한 정렬법 중에 하나입니다. 알고리즘은 배열을 두 번째 요소부터 마지막까지 각각을 첫 요소와 비교해가며 스캔합니다. 만약 현재 요소보다 첫 요소가 더 크다면 둘은 서로 swap하고 큰쪽은 점점 배열의 끝으로 움직이게 됩니다. 한번 모든 스캔을 거치고나면 가장 큰 요소는 배열의 끝에 위치합니다. 배열은 이제 마지막요소를 제외한 나머지 요소들을 대상으로 이 과정을 반복합니다. 이 과정은 가장 큰 요소 앞에 그 다음 큰 요소가 오게됩니다. 전체 요소의 수-1 만큼의 횟수만큼 반복하고 결과는 배열이 정렬되겠죠.

 

여기 함수는 정수배열을 정렬하도록 설계되었습니다. 따라서 line 1은 정수를 비교하고 line 2에서 4까지는 임시로 정수를 저장하기 위해서 임시공간을 사용합니다. 우리는 이제 원하는 것은 우리가 이 코드를 정수만으로 제한되지 않고 어떤 데이터 타입도 사용할 수 있게 바꿀 수 있는지 알아보는 것입니다.

 

동시에 알고리즘을 사용할 때마다 분석하고 싶진 않습니다. 우리는 bubble()함수 내부의 비교를 제거함으로써 실제 알고리즘과 관련된 부분의 재작성 없이 비교함수를 상대적으로 쉽게 수정함으써 시작하겠습니다. 그 결과 bubble_2.c와 같습니다:

 

/*---------------------- bubble_2.c -------------------------*/

/* Program bubble_2.c from PTRTUT10.HTM   6/13/97 */

   /* Separating the comparison function */

#include <stdio.h>

int arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(int a[], int N);
int compare(int m, int n);

int main(void)
{
    int i;
    putchar('\n');
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr,10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

void bubble(int a[], int N)

{
    int i, j, t;
    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (compare(a[j-1], a[j]))
            {
                t = a[j-1];
                a[j-1] = a[j];
                a[j] = t;
            }
        }
    }
}

int compare(int m, int n)
{
    return (m > n);
}
/*--------------------- end of bubble_2.c -----------------------*/

 

만약 우리의 목표가 데이타 타입에 독립적으로 정렬 루틴(routine)을 만드는 것이라면, 그걸 수행할 한가지 방법은 integer 데이터 타입 대신에 void 포인터 타입을 사용하는 것입니다. 포인터가 사용될 수 있도록 위의 코드에서 약간만 수정합시다. 우리는 정수 입력을 포인터로 고정할 것입니다.

 

/*----------------------- bubble_3.c -------------------------*/

/* Program bubble_3.c from PTRTUT10.HTM    6/13/97 */

#include <stdio.h>

int arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(int *p, int N);
int compare(int *m, int *n);

int main(void)
{
    int i;
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr,10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

void bubble(int *p, int N)
{
    int i, j, t;
    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (compare(&p[j-1], &p[j]))
            {
                t = p[j-1];
                p[j-1] = p[j];
                p[j] = t;
            }
        }
    }
}

int compare(int *m, int *n)
{
    return (*m > *n);
}

/*------------------ end of bubble3.c -------------------------*/

 

변화된 부분에 주목합시다. 우리는 이제 정수포인터를 (또는 정수의 배열) bubble()에 전달합니다. 그리고 bubble 내부에서 우리는 포인터를 비교 함수로 비교하길 원하는 배열의 요소로  전달합니다. 물론 이 포인터를 실제 비교를 수행하기위해서 compare()에서 역참조합니다. 다음 단계는 bubble()의 포인터를 void 포인터로 변환하는 것 입니다. 그러면 함수는 타입에 덜 민감하게 동작할 것입니다. 그것은 bubble_4.c에서 나타납니다.

 

/*------------------ bubble_4.c ----------------------------*/

/* Program bubble_4.c from PTRTUT10,HTM   6/13/97 */

#include <stdio.h>

int arr[10] = { 3,6,1,2,3,8,4,1,7,2};

void bubble(int *p, int N);
int compare(void *m, void *n);

int main(void)
{
    int i;
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    bubble(arr,10);
    putchar('\n');

    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

void bubble(int *p, int N)
{
    int i, j, t;
    for (i = N-1; i >= 0; i--)
    {
        for (j = 1; j <= i; j++)
        {
            if (compare((void *)&p[j-1], (void *)&p[j]))
            {
                t = p[j-1];
                p[j-1] = p[j];
                p[j] = t;
            }
        }
    }
}

int compare(void *m, void *n)
{
    int *m1, *n1;
    m1 = (int *)m;
    n1 = (int *)n;
    return (*m1 > *n1);
}
/*------------------ end of bubble_4.c ---------------------*/

 

이 작업을 통해 compare()함수에서 전달되는 정렬대상의 실제 타입을 void 포인터로 캐스팅을 소개했습니다. 이점을 기억하시길 바랍니다. 하지만 나중에 또 볼 수 있으니 괜찮습니다. 그리고 bubble()에 전달된 것은 정수 배열의 포인터이기 때문에 compare()를 호출할 때 들어온 인자를 void 포인터로 캐스팅 해야합니다.

 

이제 우리는 bubble()에 전달되는 것의 문제를 제기합니다. 함수의 첫 번째 인자를 void 포인터로 받기를 원합니다. 그러나 bubble()함수 내에서 현재는 정수인 변수 t에 대해 뭔가를 해야함을 의미합니다. 또한, 우리가 t = p[j-1];을 쓸 때 변수 t에 몇 byte를 복사할지 알기위해서 p[j-1]의 타입을 알 필요가 있습니다(t를 대체할 다른 변수이더라도).


bubble_4.c에서 bubble()내부에서 정렬할 데이터 타입은 첫 번째 인자의 포인터 타입은 integer라는 사실에서 얻었습니다. 만약 bubble()을 데이터 타입을 가리지 않고 정렬에 사용할 수 있다면 우리는 void 타입의 포인터를 만드는 것이 필요합니다. 그러나 그렇게 함으로써 우리는 배열 내부요소의 개개의 크기에 관한 정보를 잃을 것입니다. 그래서 bubble_5.c에서는 크기 정보에 대한 인자를 추가하겠습니다.


어쩌면 bubble_4.c에서 bubble_5.c의 변화는 이전에 만들었던 것보다 좀 더 광범위할 것입니다. 그러므로 두 모듈의 차이점을 조심스럽게 비교해봅시다.


/*---------------------- bubble5.c ---------------------------*/


/* Program bubble_5.c from PTRTUT10.HTM    6/13/97 */




#include <stdio.h>

#include <string.h>


long arr[10] = { 3,6,1,2,3,8,4,1,7,2};


void bubble(void *p, size_t width, int N);

int compare(void *m, void *n);


int main(void)

{

    int i;

    putchar('\n');


    for (i = 0; i < 10; i++)

    {

        printf("%d ", arr[i]);

    }

    bubble(arr, sizeof(long), 10);

    putchar('\n');


    for (i = 0; i < 10; i++)

    {

        printf("%ld ", arr[i]);

    }


    return 0;

}


void bubble(void *p, size_t width, int N)

{

    int i, j;

    unsigned char buf[4];

    unsigned char *bp = p;


    for (i = N-1; i >= 0; i--)

    {

        for (j = 1; j <= i; j++)

        {

            if (compare((void *)(bp + width*(j-1)),

                        (void *)(bp + j*width)))  /* 1 */

            {

/*              t = p[j-1];   */

                memcpy(buf, bp + width*(j-1), width);

/*              p[j-1] = p[j];   */

                memcpy(bp + width*(j-1), bp + j*width , width);

/*              p[j] = t;   */

                memcpy(bp + j*width, buf, width);

            }

        }

    }

}


int compare(void *m, void *n)

{

    long *m1, *n1;

    m1 = (long *)m;

    n1 = (long *)n;

    return (*m1 > *n1);

}

/*--------------------- end of bubble5.c ---------------------*/


compare()함수 내부의 변화를 설명하기위해 배열의 데이터 타입이 int 에서 long으로 변화한 내용을 주의깊게 보시길 바랍니다. bubble() 내에서 변수 t를(int에서 long으로 타입이 바뀐) 멀리했습니다. long타입을 저장하는데 필요한 크기인 unsigned char타입의 크기 4인 버퍼도 추가했습니다.(코드에서 이 부분은 차후에 더 개선해 나갈 것입니다.) unsigned char 포인터 *bp는 정렬된 배열의 처음을 가리키는데 사용했습니다.


또한 compare()에 전달한 것을 수정했습니다. 그리고 swap이 필요한 배열요소를 어떻게 swap한지를 설명합니다. memcpy()를 사용하고 배열 표기법대신 포인터 표기법을 사용하여 타입 민감도를 낮췄습니다.


다시 말씀드리지만, bubble_4.c와 bubble_5.c의 자세한 비교는 어떤일이 일어나고 왜 일어나는지에 대한 이해에 도움이 됩니다.


이제 long타입 정수 대신 string을 정렬하기위해 bubble_5.c의 bubble()함수가 동일하게 쓰이는 bubble6.c로 이동하겠습니다. 물론 long타입 정수의 비교와 string의 비교방식의 차이때문에 비교 함수를 바꿀 필요는 있습니다. 그리고 bubble6.c에서 bubble_5.c에서 주석으로 표기했던 bubble()내부의 line을 삭제했습니다.


/*--------------------- bubble6.c ---------------------*/

/* Program bubble_6.c from PTRTUT10.HTM   6/13/97 */


#include <stdio.h>

#include <string.h>


#define MAX_BUF 256


char arr2[5][20] = {  "Mickey Mouse",


                      "Donald Duck",


                      "Minnie Mouse",


                      "Goofy",


                      "Ted Jensen" };


void bubble(void *p, int width, int N);

int compare(void *m, void *n);


int main(void)

{

    int i;

    putchar('\n');


    for (i = 0; i < 5; i++)

    {

        printf("%s\n", arr2[i]);

    }

    bubble(arr2, 20, 5);

    putchar('\n\n');


    for (i = 0; i < 5; i++)

    {

        printf("%s\n", arr2[i]);

    }

    return 0;

}


void bubble(void *p, int width, int N)

{

    int i, j, k;

    unsigned char buf[MAX_BUF];

    unsigned char *bp = p;


    for (i = N-1; i >= 0; i--)

    {

        for (j = 1; j <= i; j++)

        {

          k = compare((void *)(bp + width*(j-1)), (void *)(bp + j*width));

          if (k > 0)

            {

             memcpy(buf, bp + width*(j-1), width);

             memcpy(bp + width*(j-1), bp + j*width , width);

             memcpy(bp + j*width, buf, width);

            }

        }

    }

}


int compare(void *m, void *n)

{

    char *m1 = m;

    char *n1 = n;

    return (strcmp(m1,n1));

}


/*------------------- end of bubble6.c ---------------------*/


그러나 bubble5.c에서 사용했던 그대로의 bubble()함수는 다양한 데이터 타입의 정렬을 할 수 있는 능력이 있습니다. 남은 일은 진정 범용적으로 사용하고 싶은 비교함수 이름을 bubble()함수에 전달하는 것입니다. 오직 배열의 이름은 데이터 세그먼트 내부의 배열 첫 요소의 주소입니다. 함수의 이름은 코드 세그먼트에서 그 함수의 주소로 바뀌게 됩니다. 따라서 함수 포인터의 사용이 필요합니다. 이 경우에는 비교함수가 그 대상입니다.


함수 포인터는 인자의 타입과 개수 그리고 리턴 값의 타입이 일치하는 함수와 매칭되어야 합니다. 우리의 경우, 우리의 함수 포인터를 다음처럼 선언합니다:


int (*fptr)(const void *p1, const void *p2);


주의하세요. 이렇게도 썼었습니다.


int *fptr(const void *p1, const void *p2);


우리는 int 타입 포인터를 리턴하는 프로토타입 함수를 만들 것입니다. C에서 괄호 () 기호는 포인터 * 연산자 보다 우선순위입니다. (*fptr)처럼 괄호를 넣음으로 인해 함수 포인터를 선언했음을 가리킵니다.


이제 bubble()함수의 선언을 4번째 인자에 올바른 형태의 함수 포인터를 넣어 수정합니다. 프로토타입 함수는 다음과 같습니다:


void bubble(void *p, int width int N, int (*fptr)(const void *, const void *));


bubble()을 호출할 때, 사용하길 원하는 비교함수의 이름을 넣습니다. bubble7.c는 다른 데이터 타입의 정렬을 위해 동일한 bubble()함수 사용을 어떻게 허가하는지 설명합니다.


/*------------------- bubble7.c ------------------*/


/* Program bubble_7.c from PTRTUT10.HTM  6/10/97 */


#include <stdio.h>

#include <string.h>


#define MAX_BUF 256


long arr[10] = { 3,6,1,2,3,8,4,1,7,2};

char arr2[5][20] = {  "Mickey Mouse",

                      "Donald Duck",

                      "Minnie Mouse",

                      "Goofy",

                      "Ted Jensen" };


void bubble(void *p, int width, int N,

            int(*fptr)(const void *, const void *));

int compare_string(const void *m, const void *n);

int compare_long(const void *m, const void *n);


int main(void)

{

    int i;

    puts("\nBefore Sorting:\n");


    for (i = 0; i < 10; i++)               /* show the long ints */

    {

        printf("%ld ",arr[i]);

    }

    puts("\n");


    for (i = 0; i < 5; i++)                  /* show the strings */

    {

        printf("%s\n", arr2[i]);

    }

    bubble(arr, 4, 10, compare_long);          /* sort the longs */

    bubble(arr2, 20, 5, compare_string);     /* sort the strings */

    puts("\n\nAfter Sorting:\n");


    for (i = 0; i < 10; i++)             /* show the sorted longs */

    {

        printf("%d ",arr[i]);

    }

    puts("\n");


    for (i = 0; i < 5; i++)            /* show the sorted strings */

    {

        printf("%s\n", arr2[i]);

    }

    return 0;

}


void bubble(void *p, int width, int N,

            int(*fptr)(const void *, const void *))

{

    int i, j, k;

    unsigned char buf[MAX_BUF];

    unsigned char *bp = p;


    for (i = N-1; i >= 0; i--)

    {

        for (j = 1; j <= i; j++)

        {

            k = fptr((void *)(bp + width*(j-1)), (void *)(bp + j*width));

            if (k > 0)

            {

                memcpy(buf, bp + width*(j-1), width);

                memcpy(bp + width*(j-1), bp + j*width , width);

                memcpy(bp + j*width, buf, width);

            }

        }

    }

}


int compare_string(const void *m, const void *n)

{

    char *m1 = (char *)m;

    char *n1 = (char *)n;

    return (strcmp(m1,n1));

}


int compare_long(const void *m, const void *n)

{

    long *m1, *n1;

    m1 = (long *)m;

    n1 = (long *)n;

    return (*m1 > *n1);

}


/*----------------- end of bubble7.c -----------------*/


제 10장 참조:


1. "Algorithms in C"

Robert Sedgewick

Addision-Wesley

ISBN 0-201-51425-7


출처1 : https://github.com/aleksandar-todorovic/awesome-c

출처2 : http://home.netcom.com/~tjensen/ptr/pointers.htm