ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 함수 포인터란?
    프로그래밍/C and C++ 2005. 7. 3. 01:40

    1. 함수 포인터란?

    - 프로그램에서함수의 이름은 메모리에 로드된 그 함수의 실행코드 영역의 시작주소를

       의한다.

    - 함수에 대한 포인터는 바로 함수의 시작주소값을 갖는 포인터이다.

     

    함수 포인터라는 것 역시 32비트 체제하에서 4바이트의 메모리를 갖는 포인터 변수 입니다. 일반 포인터 변수와 다른 점은 일반 포인터 변수가 변수들의 주소값을 저장하는 반면에 함수 포인터는 함수의 주소값을 갖는다는 것입니다. 함수는 code 부분입니다. 즉 프로그래머가 짠 코드가 컴파일 되어서 기계 코드로 변화된 것, 그것이 바로 code 입니다. 프로그램이 실행되기 위해서는 이 code가 메모리에 올라가 있어야 하는 것입니다. 여기서 어떤 함수의 호출은 이 code중에서 그 함수 부분으로 jump(이동) 하는 것이지요. 바로 이 함수 부분이라는 것이 그 함수의 주소값이 되는 것이고 이 함수의 주소값을 저장하는 포인터가 함수 포인터입니다.

    C 언어의 경우 함수자체를 변수로 만들수는 없습니다. 대신 함수를 포인터하는 것은 가능한데, 이것을 통해서 함수를 포인터 처럼 사용할수 있으며, 이 포인터가 가리키고 있는 곳의 함수를 실행시킬수도 있습니다.

     

    예) main 함수와 printf 함수의 시작 주소값을 출력합니다.

    #include <stdio.h>

    main()

    {

        printf("address of main : %u \n", &main);

        printf("address of printf : %u \n", &printf);

    }

     

    2. 함수 포인터의 용도

    - 함수에 접근하기 위해 사용된다.

    - 함수에 함수 자체를 실인수로 전달하기 위해 사용된다.

    - 함수의 처리결과가 함수일 때 그 함수에 대한 포인터를 돌려주기 위해 사용된다.

     

    함수 포인터에 대한 연산은 허용되지 않습니다.

     

    3. 함수 포인터의 선언

    - 다른 포인터 변수와 마찬가지로 함수 포인터도 먼저 선언하고 사용해야 한다.

    - 함수 포인터의 선언은 일반적으로 다음의 형식을 사용한다.

    자료형 (*함수포인터명)(인자목록);

      이 형식은 명시된 자료형을 돌려주고, 인자목록에 포함된 인자를 받는 함수에 대한 

      포인터를 선언한다.

    - 함수 포인터 선언의 구체적인 예

            ⓐ int (*f1)(int a);

            ⓑ char (*f2)(char *p[]);

            ⓒ void (*f3)();

      ⓐ 하나의 int형 인자를 받아들이고, int형 자료를 돌려주는 함수에 대한 포인터 f1을

          선언한다.

      ⓑ char형에 대한 포인터 배열을 인자로 받아, char형의 값을 돌려주는 함수에 대한

          포인터 f2를 선언한다.

      ⓒ아무런 인자도 받지 않고 결과값도 돌려주지 않는(void) 함수에 대한 포인터 f3를

          선언한다.

    - 함수 포인터 선언과 포인터를 돌려주는 함수 선언의 차이

            ⓐ int (*f1)(int a);

            ⓑ int *f2(int a);

      ⓐ 함수 포인터 : 한 개의 int형 인자를 받아, int형 값을 결과로 돌려주는 함수에 대한

          포인터 f1을 선언한다.

      ⓑ 포인터를 돌려주는 함수의 선언 : 한 개의 int형 인자를 받아, int형 포인터 값을

          결과로 돌려주는 함수를 선언한다.

    함수에 대한 포인터의 선언은 반드시 포인터의 이름과 간접연산자(*) 주위에 ( )를 사용해야 합니다.

     

    4. 함수 포인터의 초기화

    - 함수 포인터를 선언하고 나면, 이 포인터가 어떤 함수를 지시하도록 초기화해야 한다.

    - 함수 포인터를 초기화할 때, 인자목록과 return 자료형이 일치해야 한다.

    - 함수 이름은 이름 자체로 주소를 의미한다. 따라서 함수포인터에 함수의 주소값을

      초기화하려면 다음과 같이 한다.

      예)

      int add(int a, int b); => 함수의 prototype

      int (*f1)(int x, int y); => 함수 포인터 선언

      int add(int a, int b) { return a + b; }  => 실제 함수 정의 부분 
     
      f1 = add; => 적합

      f1 = &add; => 적합

      f1 = add(); => 오류(f1은 포인터, add()의 결과는 int)

      f1 = &add(); => 오류(&부적당)
     

     

    5. 함수 포인터의 활용

    - generic한 함수(혹은 알고리즘)의 작성을 가능하게 한다

    - 잘 사용하면 유지/보수를 수월하게 한다.

    - 함수의 이름 자체는 배열의 이름처럼 한번 정해지면 바꿀 수 없는 포인터 상수이다.

      그러나 함수 포인터는 변경이 가능하며 필요할 때마다 다른 함수를 지시하도록 설정할

      수 있다.

      예)

      #include <stdio.h>

      

      int add(int a, int b); /* 함수의 prototype */

      int sub(int a, int b);

      int mul(int a, int b);

      int div(int a, int b);

     

      main()

      {

          int (*fun)(int x, int y); /* 함수 포인터 선언 */

          int a, b;

          char c;

     

          printf("Input (num op num) : ");

          scanf("%d %c %d", &a, &c, &b);

     

          switch (c)

          {

                case '+' :

                    fun = add;

                    break;

                case '-' :

                    fun = sub;

                    break;

                case '*' :

                     fun = mul;

                     break;

                case '/' :

                     fun = div;

                     break;

          }

           printf("%d %c %d = %d\n", a, c, b, fun(a,b));

      }

     

      int add(int a, int b)

      {

          return a+b;

      }

     

      int sub(int a, int b)

      {

          return a-b;

      }

     

      int mul(int a, int b)

      {

          return a*b;

      }

     

      int div(int a, int b)

      {

          return a/b;

      }
     

      입력에 따라 함수포인터 fun에 지정되는 함수가 결정된다.


    - 함수 포인터의 배열: 여러개의 함수포인터를 배열에 저장하여 사용할 수 있다.

      int (*fun[3])(int, int);

      int 형 자료 두 개를 입력으로 받아, int형 결과를 돌려주는 함수포인터 3개를 저장할 수

      있는 배열이다.

      예) 두 정수 a,b를 읽어 합,차,곱을 구하는 예제로 함수포인터의 배열을 사용한다.

      #include <stdio.h>


      int add(int a, int b); /* 함수의 prototype */

      int sub(int a, int b);

      int mul(int a, int b);

     

      main()

      {

          char op[] = {'+', '-', '*'};

          int (*fun[])(int x, int y) = {add, sub, mul};

          int a, b;

     

          printf("Input number(2) : ");

          scanf("%d %d", &a, &b);

     

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

          printf("%d %c %d = %d\n", a, op[i], b, fun[i](a,b));

      }

     

      int add(int a, int b)

      {

          return a+b;

      }

     

      int sub(int a, int b)

      {

          return a-b;

      }

     

      int mul(int a, int b)

      {

          return a*b;

      }
     
    - 함수 포인터를 인자로 전달하기: 함수명을 인자로 전달하거나 함수 포인터 자체를

      함수의 인자로 보내고 받을 수 있다. 함수명을 실인수로 사용할 경우, 호출당한

      함수의 가인수는 함수 포인터가 된다.

      예)
      #include <stdio.h>


      int print_add(int a, int b)

      {

          printf("%d + %d = %d\n", (a, b, a+b);

      }

      int add(void (*fp)(int, int), int x, int y)

      {

          fp(x, y);

      }

     

      main()

      {

          add(print_add, 10, 5);

      }
     
    - 함수의 주소 값 전달

      add(think);     => think(); 함수의 시작 번지 값이 add() 함수의 매개변수이다.

      add(think());  => think(); 함수의 리턴 값이 add() 함수의 매개변수이다.

    - 함수 포인터를 이용해 특정 번지로 점프하기

      예1)

      #include <stdio.h>

     

      void main(void)

      {

          unsigned int goaddr = 0x8120;  /* 8120H 번지임을 나타낸다. */

          void (*gofunc)(void);              /* 함수 포인터 선언 */

     

          gofunc = (void(*)()) goaddr;    /* 초기화 */

          (*gofunc)();                          /* 함수포인터 실행 */

      }

     

      위 예에서는 void (*gofunc)(void); 로 선언된 함수 포인터가 실제적으로 가리켜야 할

      목적지 함수가 따로 없는 것처럼 보인다. 그러나 잘 보면 목적 함수는 다음과 같음을

      알 수 있다.

      void (*goaddr)();     : 목적함수

      그리고 위 목적함수는 하나의 형(type)으로써 뒤의 goaddr을 cast한다.

      이제 (*gofunc)(); 로 실행되면 컴퓨터의 PC(Program Counter) 또는

      IP(Instruction Pointer)는   (*gofunc)(); 함수로부터 void (*goaddr)(); 함수로 넘어간다.

      그리고 void (*goaddr)(); 함수의 시작번지는 0x8120 번지가 되는 것이다. 따라서

      컴퓨터의 PC는 0x8120번지로 점프하게 되는 결과를 낳는다.

     

      예2)
      #include <stdio.h>


      void main(void)

      {

          void (*gofunc)(void);          /* 함수 포인터 선언 */

          gofunc = (void (*)()) 0x8120;   /* 시작주소 초기화 */

         

          (*gofunc)();                   /* 함수포인터 실행 */

      }


      위 프로그램은 번거롭게 goaddr이라는 변수를 생략하고 직접 (*gofunc)();의 시작주소를

      지정하였다. 이로서 확실히 알 수 있는 것은 포인터 함수의 시작주소의 캐스팅 형이

      (void (*)())이 된다는 것이다.

     

      예3)단 한줄로 나타내보자.

      #include <stdio.h>


      void main(void)

      {

          (*((void (*)()) 0x8120))();

      }


      이 한줄은 바로 0x8120번지로 점프하라는 명령어와 같다!

      그러나 TurboC++ 3.0 에서는 위 명령이 유효하나 IC96에서는 무효하다.

      (다음과 같은 에러 메시지가 뜬다.)

      iC-96 FATAL ERROR --

      internal error: invalid directionary access, case 3

      COMPILATION TERMINATED


      그러나 방법은 있다. 아래와 같이 하면 IC96에서 에러 없이 멋지게 만들어낼 수 있다.

     

      #include <80c196.h>         /* 표준 인클루드 파일 */

      void main()

      {

           (*(void (*)(void))(*(void (**)(void))0x8120))();

      }

     

      실제로 위와 같은 선언은 특히 Embedded System Programming에서 많이 사용되는

      방법이다. 특히 80C196에서 이중 인터럽트 벡터 지정 시에 유용하게 사용될 수 있다.

     

    출처 : 네이버 카페 임베디드시스템(Device Control)

    댓글