언어/C언어

[C언어] 함수포인터 기본 개념과 쓰는 이유

REAKWON 2018. 9. 27. 15:44

함수포인터

우리는 이제까지 어떤 변수를 가리키는 포인터, 배열을 가리키는 포인터를 사용해보았어요. 물론 이정도를 알아도 상당히 훌륭하다고 생각합니다.

그렇지만 변수뿐만 아니라 함수도 포인터로 가리킬 수 있다는 것을 알게 된다면 조금 더 간지나는 프로그래밍을 시도해 볼 수 있겠습니다. 이제부터 설명할 것이 무엇이냐, 그 이름하여 함수포인터랍니다. 

함수포인터라... 그냥 포인터도 극혐인데 말이죠

함수포인터란 함수를 가리킬 수 있는 포인터를 의미합니다. 아니, 근데 그러면 주소를 알아야하는데, 함수에도 주소가 있나? 네, 있습니다. 함수가 주소를 가지고 있다구요? 제말이 구라가 아님을 보여드리겠습니다.

#include <stdio.h>
int sum(int a, int b) { 
        return a + b;
} 

int main() { 
        printf("함수 sum의 주소 : %p\n", &sum);
}


이 코드의 결과는 어떻게 될까요? 우리는 변수명 앞에 &(amp)를 붙여 변수의 주소를 알게 됩니다. 그렇다면 &함수명은 무엇을 의미하게 될까요? 아무리 눈치없어도 진짜 이정도면 눈치채야지~

신기하네요. 16진수로 어떤 수가 나오네요. 눈치채셨겠지만 함수의 주소를 의미하게 됩니다. 바늘가는데 실간다는데 주소가 있으면 포인터가 있겠죠. 그렇다면 함수포인터를 선언하는 방법을 알아보도록 하겠습니다. 간단합니다.

①int ②(*ptrSum)③(int a,int b)

 

일반 포인터와 마찬가지로 주소를 가리킬때는 *을 사용해서 포인터라고 알려줍니다.

①은 함수의 반환형을 의미합니다.

②는 함수포인터의 이름을 의미합니다. (변수명과 같이 임의로 정해줍니다.)

③은 매개변수를 의미합니다. 매개변수가 없을 때는 빈 괄호나 void를 사용합니다.

 

네, 위 세가지만 지켜주면 됩니다.

그러니까 ptrSum이라는 함수포인터는 반환형이 int형이고 매개변수 2개를 갖는데, 둘 다 int형 매개변수인 함수포인터가 되겠습니다.

그렇다면 다음과 같은 함수포인터는 무엇을 의미할까요?

void (*ptrFunc)()

함수명이 ptrFunc인데, 반환값이 없고(void), 매개변수도 없는 함수포인터를 의미합니다. 이제 응용가능하시겠죠? 함수포인터에서 반환형과 매개변수가 일치하는 함수만 함수포인터에 할당이 가능합니다. 기억하세요.

함수포인터를 선언하는 방법은 알게 되었으니, 다음 코드를 통해서 함수포인터의 값이 바로 그 함수의 주소인지 확인도 해보고, 사용도 해봅시다.

#include <stdio.h>
int sum(int a, int b) { 
        return a + b;
}

int main() {
        int(*ptrSum)(int a,int b); 
        ptrSum = sum; 
        printf("sum의 주소: %p\n", &sum); //&sum은 sum과 같음 
        printf("ptrSum의 값: %p\n", ptrSum);
        printf("ptrSum의 주소: %p\n", &ptrSum);
        printf("ptrSum(%d, %d) = %d\n", 3, 4, ptrSum(3, 4));
}

 

우리는 sum이라는 함수의 주소가 0x00081023번지라는 것과 ptrSum이 0x00081023을 가리키는 것을 확인할 수 있습니다(실제 sum도 함수의 주소, &sum도 함수의 주소입니다).

그리고 그 함수포인터의 주소는 0x0026FB04번지네요.

함수포인터를 통해서 호출(ptrSum(3,4)) 확인해볼 수 있군요. 

(코드에서 나오지는 않았지만 함수포인터의 크기는 역시 4바이트입니다. 포인터이기 때문에요.)

조금 더 쉽게 그림을 통해서 보도록할게요.

 

포인터를 설명할때 아주 많이 사용하는 그림이죠? 지겹죠?

0x0026FB04번지에 있는 ptrSum이라는 함수포인터는 0x00081023번지에 있는 함수 sum의 시작주소를 가리키게 됩니다.

sum은 함수이기 때문에 시작주소를 갖고 있고, 그 주소를 기점으로 매개변수를 할당, 그리고 함수 내부의 변수들을 스택에 따라 쌓아올립니다. 그러니까 함수의 시작주소를 통해 변수의 주소를 알게 됩니다. 함수의 시작주소는 중요하단 거죠. (더 자세하게 함수호출과정을 아는 것도 도움이 됩니다. 구글형님께 물어보세요.)

그런 sum의 시작주소를 ptrSum은 알고 있기 때문에 sum함수를 호출할 수 있는 겁니다.

아니, 그렇다면 sum함수만 호출하면 되지, 왜 굳이 포인터를 써서 호출하는 거야?

저도 무척이나 이런 생각을 하고 하고 또 하면서 그냥 함수포인터를 흘려 넘겼었죠.

근데 쓰임새는 꽤나 많습니다. 예를 들면 함수 자체를 매개변수로 받고 싶을때가 있을 겁니다.

프로젝트를 진행할때 한 사람만이 진행한다면 물론 상관이 없지만, 여러 사람과 협업을 해야하거나, 라이브러리를 제공할때, 함수에 함수자체를 매개변수로 받아야할 때가 있습니다. 누가 어떤 함수를 필요로 할지 모르니, 어떤 형식으로 함수를 정의해서 매개변수로 전달하게 되면 그 함수를 내부에서는 호출하게 되는 식으로 말이죠.

void func(void (*ptr)()) {
...      ptr();       ... 
}

그리고 또 구조체에서 멤버함수를 커스터마이징할때도 사용할 수가 있습니다. 

struct animal{     
...       void (*walk)();       ... 
}

 

위에서 walk라는 멤버함수를 우리가 원하는 대로 기능을 정의해줄 수 있습니다. 물론, 함수포인터의 형식과 맞는다면 말이죠. 이런 방식은 자바에서 interface라고 하여 메소드를 구현하는 방식과 비슷해보입니다. 함수포인터를 더 연습해보고 개념을 확실히 이해하세요.

이것으로 함수포인터에 대해 알아보았습니다.

반응형