티스토리 뷰


c언어 - 함수포인터 (Function pointer)

오늘은 함수포인터에 대해서 포스팅을 하도록 하겠습니다.

포인터가 무엇인지는 다들 아실텐데요, 특정 변수에 대한 메모리 주소를 담을 수 있는 변수를 포인터 변수라고 합니다. 그렇다면 함수포인터란, 특정 함수에 대한 메모리 주소를 담을 수 있는 것 이라고 정의할 수 있겠습니다.

함수포인터를 쓰는 이유는 무엇일까요?

  1. 프로그램 코드가 간결해집니다.
  2. 함수포인터를 배열에 담아서도 사용할 수 있으므로 중복되는 코드를 줄일 수 있습니다.
  3. 상황에 따라 해당되는 함수를 호출할 수 있으므로 굉장히 유용합니다.

그 외에도 함수 포인터를 이용하여 콜백함수를 구현할 수 있게 되는 등 편리하고 유용한 코드를 작성할 수 있게 됩니다.


우선 함수포인터의 모양에 대해 알아보도록 하겠습니다.

int (*FuncPtr) (int, int)

함수포인터는 위와 같은 모양을 띕니다. 함수의 프로토타입 선언과 모양이 비슷하죠?


함수의 프로토타입과 다른점이 있다면 함수 이름앞에 포인터를 가르키는 *이 붙는 다는 것인데요. 이렇게 선언이 되게 되면 FuncPtr 이라는 함수의 주소를 담을수 있는 '변수'가 생기는 것입니다. 이 FuncPtr 함수포인터가 담을 수 있는 함수는 위와 같은 모양을 띄어야 합니다.


즉, 함수의 리턴형은 int 여야하고, int형 파라미터 2개를 받는 함수여야 하는 것입니다.


예를 들어,

int add (int first, int second)
double div (double first, double second)

위의 보이는 두 함수가 있다고 가정할 때, 함수포인터의 선언 모양과 똑같이 생긴 add 라는 함수의 주소만을 담을 수 있는 것입니다. 아래의 사용 예제를 한 번 더 보시겠습니다.

1. FuncPtr = add    (o)
2. FuncPtr = &add   (o)
3. FuncPtr = div    (x)
4. FuncPtr = add()  (x)

1, 2 : 2가지 방법 모두 괜찮은 사용 방법입니다. 어떤 것을 쓰셔도 무관합니다.

3 : div는 FuncPtr의 선언 모양과 프로토타입이 달라서 사용할 수 없습니다. 에러가 발생합니다.

4 : add() 처럼 함수를 호출할 때 처럼 쓰는 것은 결과값이 함수의 호출 이후 리턴 값이 되는 것입니다. 따라서 add() 는 int형을 가리키게 되는 것이므로 사용방법 자체가 잘 못 되었습니다. 에러가 발생합니다.

이렇듯 함수포인터는 담고 싶은 함수의 프로토타입을 따라 선언하여 사용하시면 됩니다. 하지만, 이 모양이 복잡하기 때문에 typedef를 이용하여 타입의 모양을 단순화 시키는 작업을 해 줄수도 있습니다.

typedef int (*FuncPtr)(int, int)

프로그램 상단에 위와 같이 선언한 후, 실제 사용을 하실 때에는 FuncPtr 이라는 Type으로 새로운 변수를 사용하실 수 있습니다.

FuncPtr testFP = NULL;
testFP = add;

이렇게 말이죠. 아, 참고로 모든 변수 특히 포인터 변수를 선언해 주실 때, 초기화 해주는 습관은 정말 좋은 습관이십니다 :) 크리티컬 에러를 미리 예방할 수 있는 방법 중 하나입니다.

예제 소스 코드

자 이제 마지막으로, 함수포인터를 이용해서 만든 실제 예제를 한 번 보여드리도록 하겠습니다.

#include <stdio.h>

// 함수포인터 타입 정의
typedef int (*calcFuncPtr)(int, int);


// 덧셈 함수
int plus (int first, int second)
{
    return first + second;
}
// 뺄셈 함수
int minus (int first, int second)
{
    return first - second;
}
// 곱셈 함수
int multiple (int first, int second)
{
    return first * second;
}
// 나눗셈 함수
int division (int first, int second)
{
    return first / second;
}

// 매개변수로 함수포인터를 갖는 calculator 함수
int calculator (int first, int second, calcFuncPtr func)
{
    return func (first, second);     // 함수포인터의 사용
}

int main(int argc, char** argv)
{
    calcFuncPtr calc = NULL;
    int a = 0, b = 0;
    char op = 0;
    int result = 0;

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

    switch (op)    // 함수포인터 calc에 op에 맞는 함수들의 주소를 담음
    {
        case '+' :
            calc = plus;
            break;

        case '-':
            calc = minus;
            break;

        case '*':
            calc = multiple;
            break;

        case '/':
            calc = division;
            break;
    }

    result = calculator (a, b, calc);

    printf ("result : %d", result);

    return 0;
}

실행결과는 다음과 같습니다.

간단한 소스코드라 어려운 점은 없을 겁니다. 혹시 코드에 이해 안가는 부분이 있다면 댓글 남겨주시면 바로 답변 드립니다.

신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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

c/c++ sprintf, snprintf 함수  (3) 2014.01.21
C언어 qsort() 함수  (3) 2014.01.02
배열의 개수를 세는 _countof 매크로  (0) 2013.12.31
c언어 - 함수포인터 (Function pointer)  (14) 2013.12.30
c언어 함수에 대한 기본 (2)  (0) 2013.06.14
c언어 함수에 대한 기본 (1)  (0) 2013.06.11

댓글
  • 프로필사진 쎾쓰 add() 설명좀 쉽게해주새요ㅎ 2014.08.12 21:21 신고
  • 프로필사진 norux 죄송합니다. 좀 늦었습니다.
    함수포인터에 함수를 담는 부분 질문해주신것 같습니다.

    함수포인터에는 함수의 주소를 담을 수 있습니다.
    따라서 함수 명인 add의 주소표현식인 &add 를 담는 것이 원칙입니다만, 그냥 & 기호를 떼어 add를 담더라도 정상적으로 담기게 됩니다.

    하지만 add () 의 경우는 다릅니다. 함수 포인터에 함수의 주소를 담아야 하는데, 함수의 호출 값을 담는다는 의미가 됩니다. 즉, 함수를 호출하고 리턴 받은 값을 담는다는 이야기이지요.
    (사실 파라미터 2개를 인자로 받는 add 함수를 add ()로 호출하는 것 자체가 오류이기도 합니다. 그렇다고 add(3,5) 를 한다해서 정상적이다라는 의미는 아닙니다. 아시다시피 add(3,5)를 호출하게 되면 8이라는 값을 받게 되겠죠.)


    참고로 리턴값의 자료형이나 파라미터 자료형이 다른 함수도 함수포인터에 담을 수 있습니다만, 원치않은 값으로 변할 확률이 무궁무진합니다.(그래서 사용하지 않습니다.)

    하지만 때로 파라미터 개수가 부족한 함수를 담는 경우는 있습니다.
    예) int isNum (int n) => FuncPtr = isNum

    이 때에는 처리되지 않은 두 번째 인자 때문에 오류가 발생할 확률이 있으므로 반드시 오류조건을 잘 처리해주어야 합니다.
    2014.08.25 22:37 신고
  • 프로필사진 Genesis8 감사합니다. 덕분에 쉽게 쉽게 알아갑니다. 2015.02.04 12:57 신고
  • 프로필사진 띵사마 main함수의 두번째 매개변수의 **이 뭘 의미하나요? 2015.08.20 07:58 신고
  • 프로필사진 norux 커맨드라인에서 프로그램을 실행할 때는,
    프로그램 인자(Program Arguments)라고 불리는 매개변수들을 넘겨줄 수 있습니다.

    a.out 에 프로그램 인자를 담아 실행시키는 예
    ex) a.out first second

    그러면 이제 main으로 arguments가 넘어오게 되는데요,
    main의 첫번째 인수인 argc는 프로그램 인자의 개수를 담습니다.
    위의 예제를 기준으로하면 argc에는 3이 담깁니다. (프로그램 이름까지 포함)

    그리고 사실, char** argv 보다는 char* argv[] 가 더 정확한 의미이기는 합니다. char* argv[] 로 바꿀경우 의미가 보다 명확해지기 때문이죠.
    char* 즉, 문자열을 받는 배열입니다.
    위의 예제 기준으로 argv[0] = "a.out", argv[1] = "first", argv[2] = "second"
    이 담겨있게 됩니다.
    2015.08.20 09:12 신고
  • 프로필사진 AlexOH 안녕하세요
    예제 Line 31 줄에서
    return func (first, second)
    실행되면
    func 함수는 무엇을 실행하여 반환 값은 무엇인가요?
    2016.08.03 17:55 신고
  • 프로필사진 norux 답변이 늦어 죄송합니다~
    caclulator함수를 호출할 때, 세 번째 인자로 받은 func함수를 실행하게 됩니다. 이른바 콜백함수라고 합니다.

    콜백함수에 담긴 내용은 43라인, switch-case 문에서 calc 함수포인터에 plus / minus / multiple / division 이라는 함수를 선택적으로 담았는데, 이 함수를 수행하게 되는것입니다. 리턴되는 값은 당연히 해당 함수의 리턴값입니다.
    2016.08.11 10:58 신고
  • 프로필사진 김용욱 마침 오늘 학교에서 함수 포인터의 개념을 배웠는데 잘 이해가 되질 않아 어리둥절하더군요. 근데 이 게시글을 읽고 이해 했습니다 ㅎㅎ 2016.12.07 12:12 신고
  • 프로필사진 김용욱 추가 질문이 있는데

    본문의 메인함수에서
    calcFuncPtr calc[4] = { plus, minus,
    division, multiple };

    이런식으로 초기화 해서
    calc [index] 로 호출하는 것도 가능한것인가요??
    2016.12.07 12:22 신고
  • 프로필사진 norux 네 당연합니다!
    그것도 아주 좋은 용법인 것 같습니다.

    calc[0](1, 2) => 3

    감사합니다. :)
    2016.12.13 09:50 신고
  • 프로필사진 정지연 typedef int Func( int num); 과 typedef int (*Func)(int) 는 어떻게 다른건가요? 2017.02.10 10:13 신고
  • 프로필사진 norux 답변이 조금 늦어 죄송합니다.

    타입을 일반 변수 타입으로 정의하느냐,
    포인터 변수 타입으로 정의하느냐의 차이입니다.

    만약 typedef int Func(int) 와 같이 정의하고 싶으시다면, 위 코드상에서 main에 구현되어있는

    calcFuncPtr calc = NULL;
    이 부분을 포인터로 선언해주어야 합니다.

    calcFuncPtr *calc = NULL;

    즉, 두 방법은 타입을 정의하는 방법에 차이가 있을 뿐입니다. ^^
    2017.02.15 11:19 신고
  • 프로필사진 잡둰 설명을 너무 잘해주셔서
    이거 보고 응용해 봤는데
    #include<stdio.h>
    typedef int (*funcPtr)(int, int);
    typedef struct{
    int (*function)();
    } state_table;


    int add(int x, int y){
    return x+y;
    }

    int main(void){
    state_table *s;
    funcPtr ptr = add;
    s->function = ptr;
    printf("%d\n", (*s->function)(1,2));
    return 0;
    }
    이렇게도 되네요
    2017.03.21 18:29 신고
  • 프로필사진 norux 네, 코드를 구조화시키기 좋은 방법이네요. ^^

    함수포인터도 단순히 '변수' 일 뿐이라서, 변수가 사용될 수 있는 곳이라면 어디에서도 사용할 수 있습니다!
    c++의 class의 멤버변수로도 사용할 수 있겠죠 :)
    2017.03.22 09:15 신고
댓글쓰기 폼