[C언어/C++] 함수 포인터 사용법 & 예제 총정리

    함수의 주소

    변수를 선언하면 메모리 공간이 할당되고 그 공간의 위치가 주소로 존재하듯이 함수를 선언해도 변수와 마찬가지로 메모리에 공간이 할당되며 그 위치를 표현하는 주소가 생겨납니다. C언어 코드는 컴파일이 되면 기계어로 변경되고 프로그램이 실행되면 코드 세그먼트라는 메모리 영역에 위치하게 됩니다. 즉, 함수의 형태는 변경되겠지만 결국 메모리에 저장되기 때문에 주소를 가지게 된다는 의미입니다.

    #include <stdio.h>
    
    void print_hello()
    {
        printf("Hello, world!\n");
    }
    
    int main()
    {
        // 메모리 상에 저장된 함수의 주소값
        printf("함수의 주소값 : %p\n", print_hello);
    
        return 0;
    }
    

    위의 코드의 실행 값과 같이 함수도 주소 값을 가진다는 것을 확인할 수 있습니다.

     

    포인터 변수란?

    위에서 설명했듯 포인터는 변수의 주소만 저장했다가 사용할 수 있는 것은 아닙니다. 따라서 함수 포인터는 함수의 주소를 저장했다가 해당 주소의 함수를 호출하는 데 사용하는 포인터를 말합니다. 

    반환형식 (* 식별자) (파라미터형 목록)
    

    위와 같은 형식으로 사용합니다.

     

     포인터 변수 사용법 

    반환값과 매개변수가 없는 경우

    #include <stdio.h>
    
    void print_hello()
    {
        printf("Hello, world!\n");
    }
    int main()
    {
        void (*fp)(); //반환값과 매개변수가 없는 함수 포인터 fp 선언
    
        fp = print_hello; //print_hello 함수의 메모리 주소를 함수 포인터 fp에 저장
        fp(); //함수 포인터로 print_hello 함수 호출
    
        return 0;
    }
    

    인자값을 받지 않고 값을 반환하지 않는 함수를 가리키는 print_hello 함수를 만들고 함수 포인터 fp를 선언한 뒤 이 포인터를 print_hello() 함수의 주소로 초기화합니다. 이 포인터는 print_hello() 함수의 주소값을 저장하고 있기 때문에, fp 포인터를 역참조하여 함수를 호출할 수 있습니다.

     

    함수 포인터를 선언할 때는 함수 포인터와 저장될 함수의 반환값의 자료형 그리고 매개변수 자료형과 개수가 일치해야 합니다. 여기서는 반환값과 매개변수가 없는 함수를 저장할 것이므로 void로 지정합니다. 또한, 매개변수가 없으므로 ()만 붙이면 됩니다. 그리고 (*fp)와 같이 함수 포인터 이름 fp 앞에 *를 붙이고 괄호로 묶어줍니다.

     

    반환 값과 매개변수가 있는 경우

    #include <stdio.h>
    
    int add(int a, int b) // 덧셈함수
    {
        return a + b;
    }
    
    int sub(int a, int b) // 뺄셈함수
    {
        return a - b;
    }
    
    int main()
    {
        int (*fp)(int, int); //함수 포인터 선언
    
        fp = add;   //add 함수의 메모리 주소를 함수 포인터 fp에 저장
        printf("결과 값 : %d\n", fp(10, 20)); //add 함수를 호출
    
        fp = sub;   //sub 함수의 메모리 주소를 함수 포인터 fp에 저장
        printf("결과 값 : %d\n", fp(10, 20)); //sub 함수 호출
    
        return 0;
    }
    

    위의 예제에서는 add와 sub함수 둘 다 int형 인자 값 2개가 필요하고 int형으로 반환을 해야 합니다. 그렇기에 함수 포인터를 선언할 때 반환값은 int형으로 지정하고 괄호 안 인자값에 int형을 두 개 넣어줍니다.

     

    함수 포인터 배열 사용하기

    #include <stdio.h>
    
    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;} //나눗셈함수
    
    int main()
    {
        int (*fp[4])(int, int);  //함수 포인터 배열 선언
    
        fp[0] = add; // 배열[1]에 덧셈 함수의 메모리 주소 저장
        fp[1] = sub; // 배열[2]에 뺄셈 함수의 메모리 주소 저장
        fp[2] = mul; // 배열[3]에 곱셈 함수의 메모리 주소 저장
        fp[3] = div; // 배열[4]에 나눗셈 함수의 메모리 주소 저장
    
        for (int i = 0; i < 4; i++) {
            printf("배열[%d] 함수의 실행 값 : %d\n",i, fp[i](20, 10));
        }
    
        return 0;
    }
    

    함수 포인터를 배열로 만들어 사용할 수 있습니다. 일반 배열과 사용법은 동일하며 배열 인덱스에 각각의 함수를 지정하고 사용하면 됩니다.

     

    typedef로 함수 포인터 간소화 하기

    typedef int (*PtrFunc)(int, int)
    
    PtrFunc fp = NULL;
    fp = add;
    

    함수 포인터가 문법이 다소 난해하기에 위와 같이 typedef로 지정해준 뒤 사용하시면 편리합니다. C++11에서 부터는 using을 사용해서 함수 포인터 타입에 대한 별칭을 만들 수도 있습니다.

     

    포인터 함수를 사용하는 이유

    C언어에서 문장에 주소는 존재하지 않습니다. 문장의 위치를 나타내는 것에 라벨을 사용을 사용하며 문장 단위의 이동을 하려면 이것으로 충분하기 때문입니다. 하지만 함수의 경우는 함수명에 의한 호출은 충분하지 않을 수 있습니다. 함수명을 사용하여 함수를 호출하는 방법은 호출할 함수가 컴파일 시에 결정되어야 한다는 것인데 이것은 동시에 호출할 함수를 실행할 때에 동적으로 변경할 수 없는 것을 의미합니다. 이렇게 된다면 프로그램의 가능성을 크게 제한해 버리는 것입니다. 하지만 함수 포인터를 이용해서 함수 이름을 운영체제에 전달하면 운영체제는 우리를 대신하여 함수를 호출해 줍니다. 이러한 특성을 가지고 있는 포인터 함수를 사용하여 실행 시에 호출할 함수를 동적으로 변경시킬 수 있다면, 기능을 바꾸거나 부분적인 갱신이 가능한 유연성 있는 시스템을 구축할 수 있습니다. 그래서 포인터 함수는 개발자가 자유롭게 기능을 확장할 수 있는 시스템을 구축할 때에도 사용됩니다. 함수의 포인터 배열을 제공하고, 필요에 따라서 이를 개발자가 함수의 포인터를 등록합니다. 그런 뒤 시스템은 처리를 할 때에 배열로부터 순서대로 함수를 호출합니다. 이 함수의 연계에 의해 함수의 호출을 자유롭게 연쇄시킬 수 있기 때문에, 시스템은 확장성이 높고 유연하게 됩니다. 또한 자주 사용하는 함수의 시작 주소를 배열에 저장해서 사용하면 일반적인 함수 호출보다 빠른 처리 속도를 기대할 수도 있습니다.

    댓글(0)

    Designed by JB FACTORY