ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [펌] 함수포인터 정의
    Computer Language/C 2007. 8. 24. 16:48
    728x90

    출처 : http://blog.naver.com/itsatan


    여기서는 함수 포인터에 대해 소개하고 개념적인 활용 방안에 대해서도 연구해 보기로 한다. 포인터라는 것이 원래 C/C++ 으 주제 중 가장 어렵고 난해한데 그중에서도 함수 포인터는 난해 함의 절정에 있다고 평가되는 어려운 주제이다. 처음부터 함수 포인터를 다 이해하기는 어려우므로 부담갖지 말고 가벼운 마음으로 읽어보기 바란다. 다행이 함수 포인터는 실전에서 사용빈도가 그리 높지 않으므로 모른다고 해서 당장 불편한 정도는 아니다.

    함수 포인터란 함수를 가리키는 포인터이다. 포인터란 본래 메모리상의 번지를 저장하는 변수인데 함수 메모리에 존재하며 시작 번지가 있으므로 포인터 변수로 가리킬 수 있다. 일반적인 포인터는 변수가 저장되어 있는 번지를 가리키지만 함수 포인터는 함수의 시작 번지를 가리킨다는 점에서 다르다. 함수 포인터와 구분하기 위해서는 변수를 가리키는 일반적인 포인터를 특별히 데이터 포인터라고 부르기로 한다.

    정수형을 가리키는 int *pi는 정수형 변수의 번지를 가리키며 실수형을 가리키는 double *pd 는 실수형 변수의 번지를 가진다. 데이터 포인터는 단순히 가리키는 대상체의 타입만 밝히면 되므로 선언 형식이 간단하다. 반면 함수 포인터의 대상체인 함수는 형식이 좀 더 복잡하기 때문에 선언 형식도 다소 복잡하다. 대상체가 되는 함수으 리턴 타입과 인수들의 목록까지도 같이 밝혀야 한다. 함수 포인터를 선언하는 형식은 함수의 원형 선언 형식고 유사하다. 기본 형식은 다음과 같다.


    리턴타입 (*변수명) (인수의 목록);


    함 수의 원형을 써 놓고 함수명응ㄹ 변수명으로 바꾸고 앞에 *를 붙인 후 *변수명을 괄호로 싸면 된다. 함수 포인터도 변수이므로 고유한 이름을 가져야 하며 명칭 규칙에 맞게 적당히 이름을 붙여야 한다. 리턴값과 인수 목록은 그대로 유지하되 단 형식 인수의 이름은 생략해도 상관이 없다. 함수 원형 선언에서 형식 인수으 이름이 별 의미가 없듯이 함수 포인터를 선언할 때도 인수의 타입만 의미가 있다. 다음 함수를 가리키는 함수 포인터를 선언한다고 해 보자.


    int func(int a);


    정수형 인수를 하나 취하며 정수형을 리턴하는 func 라는 함수의 원형이다. 이런 함수를 가리킬 수 있는 함수 포인터 pf를 선언하는 절차를 다음과 같다.


    1> int pf(int a);   // 함수명을 변수명으로 바꾼다.

    2> int *pf(int a);   // 변수명 앞에 *를 붙인다.

    3> int (*pf)(int);    // 변수를 괄호로 싼다. 형식 인수의 이름은 생략 가능하다.


    func 가 (*pf) 로 바뀌었는데 이름 바꾸고 *를 붙인 후 괄호를 싸기마 하면 된다. 이 선언문에서 괄호를 빼버리면 정수형 포인터를 리턴하는 함수가 되어 버리므로 괄호를 빼먹지 않도록 주의 해야한다. 몇 번 연습해 보면 단계를 거치지 않고도 곧바로 함수 포인터를 만들 수 있을 것이다.


    void func(int a, double b);   :   void (*pf)(int, double);

    char *func(char *a, int b);   : char(*pf)(char*, int);

    void func(void);   :   void (*pf)(void);


    이 렇게 선언한 함수 포인터는 자신과 원형이 같은 함수의 시작 번지를 가리킬 수 있는데 단순히 함수의 이름을 대입하면 된다. 다음 대입식은 int func(int a) 함수를 가리킬 수 있는 함수 포인터 pf에 func의 번지를 대입한다.


    pf = func;


    이 런 대입이 가능한 이유는 괄호없이 단독으로 사용된 함수명은 함수의 시작 번지를 나타내는 포인터상수이기 때문이다. 마치 배열명이 첨자없이 사영되면 배열의 시작번지를 타나내는 포인터 상수가 되는 것처럼 말이다. func라는 표현식 자체가 함수 포인터 상수이므로 함수 포인터 변수 pf가 그 번지를 대입받을 수 있다. 함수 이름 자체가 포인터 타입이므로 pf = &func; 처럼 & 연산자를 사용할 필요가 없다. pf자체는 변수이므로 원형만 일치한다면 다른 함수를 가리킬 수도 있다.

    함수 포인터에 함수의 시작번지를 저장했으면 잊 ㅔ함수 대신 포인터로 함수를 호출 할 수 있다. 변수의 번지를 가리키는 데이터 포인터로변수값을 읽을 수 있듯이 함수 포인터로는 이 포인터 가 가리키는 번지으함수를 호출할 수 있는 것이다. 함수 포인터로 함수를 호출하는 형식은 다음 두 가지가 있는데 func 함수를 가리키는 pf 로 func 함수를 호출하는 예이다.


    (*pf)(2);

    pf(2);


    함 수 포인터 다음에 인수의 목록을 나열하는데 func 함수는 정수형 인수 하나를 취하므로 상수 2를 넘겨 주었다. 물론 3이나 5또는 정수와 호환되는 변수를 인수로 넘길 수도 있다. pf가 func의 번지를 가지고 있으나 *pf는 곧 func와 동일하며 그래서 (*pf)(2)는 func(2)와 같다. 이때 *pf를 감싸는 괄호는 생략할 수 없는데, *pf(2)와 같이 쓰면 *연산자보다 () 연산자가 더 순위가 높아 pf(2)호출문이 리턴하는 포인터로 부터 대상체를 읽는 문장이 되어 버린다. *연산자가 먼저 실행되어 이 포인터가 가라키는 함수를 찾은 후 이함수를 찾은 후 이 함수를 인수로 넘겨야 한다.

    (*pf)(2) 호출 구민이 문법상 원친ㄱ적으로 맞는 방법이지만 괄호화 *연산자를 쓰는 것이 번거롭기 때문에 C컴파일러들은 좀더 간랴고하된 호출 방법을 지원하는데 그것이 바로 아래쪽에 있는 pf(2) 호출 형식이다. 함수 포인터를 마치 함수인 것처럼 쓰고 괄호안에 인수를 넘긴다. 문법적으로 엄격하게 따지자면 pf(2) 형식은 잘못된 것이지만 컴파일러가 특별히 이런 예외를 인정한다. pf가 함수 포인터라는 것을 컴파잉ㄹ러가 알고 있으므로 굳히 *연산자를 () 괄호가 쓰지 않아도 pf가 가리키는 함수 호출하는 문장이라는 것을 알 수 있으며 모호하지 않다. 그래서 함수 포인터를 함수와 동일한 방법으로 사용하는 것을 허용한다.

    함수 포인터로부터 함수를 호출하는 두 형식에 대해서는 학자마다 의견이 분분하며 아직까지도완전히 통일되어 있지 않다. 원칙대로라면 (*pf)(2) 형식이 옳으며 실제로 클래식 C는 이 형식만 인정하지만 ANSI C 이후는 둘다 인정이 된다. (*pf)(2) 호출문과 pf(2) 호출문은 컴파일러의 예외 인정에 의해 완전히 동일하며 최근에는 pf(2) 형식이 더 우세하다. 둘 중 마음에 드느 ㄴ형식을 사용하되 아무래도 후자가 더 간편하기는 하다. 다음 에제는 함수 포인터를 사용하여 함수를 호출하는 예를 보여준다.


    #include <Turboc.h>

    int func(int a)
    {
     return a*2;
    }

    void main()
    {
     int i;
     int (*pf)(int a);
     pf = func;
     i = (*pf)(2);
     printf("Result : %d\n", i);
    }


    func 함수는 정수값 하나를 인수로 받아 이 값의 2배값을 리턴하는 아주 간단한 함수이다. main에서는 이런 형식의 함수를 가리킬 수 있는 함수 포인터 pf를 선언했으며 pt에 func으 시작 번지를 대입했다. 그리고 (*pf)(2) 호출문으로 func 함수를 호출하여 그 리턴값을 지역변수 i에 대입한 후 출력했다. 인수로 2를 전달했으므로 출력 결과는 물론 4이다. (*pf)(2) 호출문을 pf(2) 로 바꾸어도 동일하다.

    pf가 가리키는 함수는 정수형 인수 하나만 취하도록 되어 있으므로 pf를 통해 함수를 호출할 때도 원형이 맞게 호출해야 한다. (*pf)(2.5) 호출은 경고로 처리되며 (*pf)("string") 호출은 컴파일 에러로 처리되는데 func(2.5)나 func("string") 호출문에서 경고나 에러가 나는 것과 같은 이유이다. 컴파일러가 이런 에러 처리를 하기 위해서 함수 포인터의 대상체 함수가 어떤 이수를 취하고 리턴값의 타입이 무엇인지를 정확하게 알아야 하며 그래서 함수포인터를 선언하는 문장이 다른 변수 선언문보다 더 복잡한 것이다.

    댓글

Designed by black7375.