jeongjinyeong's homepage

2026-09-13

C 함수 포인터

함수 포인터란 함수의 코드가 있는 주소를 담는 변수이다. 개념적으로는 너무 단순 명료해서 바로 예시를 보도록 하겠다.

int *func();
int (*func)();

이들 중 함수 포인터는 어떤 것일까? int (*func)();이 함수 포인터다.

선언 읽는 법

함수 포인터가 아닌 첫 번째 형태를 먼저 보자.

C 선언은 선언되는 이름에서 출발해, 오른쪽에 붙은 ()나 []를 먼저 읽고 그 다음 왼쪽의 *를 읽는다. ()와 []가 *보다 우선순위가 높기 때문이다. (연산자 우선순위에 대해서는 본문의 말미에 첨부하겠다.)

따라서 첫 번째 형태는 func()을 먼저 묶어주면 이것이 함수라는 것을 알 수 있고, 앞에 남은 int *가 해당 함수의 반환형이겠구나 생각해볼 수 있다.

int (*func)();

두 번째의 경우 괄호로 *와 func을 묶어주었다. 그래서 이를 해석해보면 (*func) 이것은 포인터이고, (*func)() 그 포인터가 가리키는 것은 함수이고, int (*func)() 그 함수는 int를 반환한다. 즉, “int를 반환하는 함수를 가리키는 포인터”가 된다.(여기서 ‘가리킨다’는 ‘대상의 주소를 저장한다’와 동치이다.)

이제 int *(*func)(); 이런 구조의 코드를 만나게 되더라도 어렵지 않게 해석할 수 있을 것이다.

“int 포인터를 반환하는 함수를 가리키는 포인터” func.

함수 포인터는 왜 필요한가

함수 포인터에 대한 정의도 알겠고 그 형태도 알아봤는데, 그럼 이 함수 포인터가 왜 필요한 걸까? 함수 포인터를 쓰면 어떤 이점이 있지?

함수 포인터는 말 그대로 함수를 가리키는 포인터이기 때문에, 함수 포인터가 가리키는 함수의 주소만 바꿔주면 해당 함수를 호출할 수 있다. 즉, 호출하는 코드는 그대로 두고 실행될 함수만 실행 시점에 교체할 수 있다.

여기서 한 가지 주의할 점은 함수 포인터에 담기는 함수는 함수 포인터와 매개변수·반환값의 개수와 자료형이 일치해야 한다는 것이다. 함수 포인터의 타입(매개변수·반환값의 개수와 자료형)은 그 포인터를 통해 호출할 때 컴파일러가 인자를 어떻게 넘기고 반환값을 어떻게 받을지를 결정한다. 그래서 담기는 함수의 타입이 포인터의 타입과 일치해야 하고, 다르면 컴파일러가 경고를 낸다. (강제로 캐스팅해 호출하면 미정의 동작)

함수 포인터의 활용 예시를 보겠다.

int add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}

int main() {
    int (*func)(int, int);
    func = add;
    int sum = func(1, 2);    // sum = 1+2
    func = sub;
    int diff = func(1, 2);   // diff = 1-2

    // 함수 포인터 배열을 만들어 인덱스로 편리하게 사용하는 것도 가능하다. (디스패치 테이블)
    int (*farr[2])(int, int) = {add, sub};
    farr[0](1, 2);   // add(1, 2)
    farr[1](1, 2);   // sub(1, 2)

    return 0;
}

이처럼 매개변수와 반환값의 자료형과 개수만 맞으면 함수 포인터를 사용할 수 있다.

그리고 위 예제에서는 함수의 주소를 전달할 때 &를 쓰지 않고 함수명을 그대로 썼는데, 함수 이름은 표현식 안에서 자동으로 그 함수의 주소로 변환되므로 func = add;와 func = &add;는 같다. 역참조 *add는 함수 자체가 되지만 그것이 다시 주소로 변환되므로 *add, **add도 결국 같은 주소다. 호출도 마찬가지로 func(1, 2)와 (*func)(1, 2)가 동일하다.

콜백 함수

이외에도 콜백 함수(다른 함수에 전달되어 그 함수가 필요한 시점에 되부르는(call back) 함수)에서도 사용된다.

파이썬에서 sort 메서드를 사용할 때 key로 함수를 전달하는 것과 같이, C에서도 qsort 함수에서 대소 비교를 위해 함수 포인터를 사용한다.

void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *));

여기서 (*cmp)가 함수 포인터라는 것을 알 수 있다. 해당 위치에 정렬 대상들의 우선순위 비교를 위한 함수를 넣어 특정 키값으로 정렬하거나 정렬 방향을 바꾸는 등의 작업을 처리할 수 있다.

구조체와 함수 포인터

그리고 구조체 내부에 함수 포인터를 두고 객체마다 함수 포인터에 다른 함수를 줘서 다형성을 갖는 것도 가능하다. 구조체 함수 포인터를 호출할 때 파이썬과 달리 self가 자동으로 넘어가지 않기 때문에, 구조체 내부의 값을 조작하려면 호출 시 obj->func(obj, ...)와 같이 객체를 같이 넘겨줘야 한다. (구조체 데이터를 쓰지 않는 함수라면 넘길 필요가 없다 — 파이썬의 @staticmethod에 해당한다.)

이상으로 함수 포인터에 대한 설명을 마치겠다.

연산자 우선순위

순위 연산자 결합 방향
1 () 호출, [], ->, ., 후위 ++ -- 좌→우
2 단항: * 역참조, & 주소, !, ~, 전위 ++ --, + -, sizeof, (type) 캐스팅 우→좌
3 * / % 좌→우
4 + - 좌→우
5 << >> 좌→우
6 < <= > >= 좌→우
7 == != 좌→우
8 & 비트 AND 좌→우
9 ^ 좌→우
10 \| 좌→우
11 && 좌→우
12 \|\| 좌→우
13 ?: 우→좌
14 = += -= *= /= %= <<= >>= &= ^= \|= 우→좌
15 , 좌→우

부족한 내용이나 잘못된 정보가 있다면 jeong.jinyeong95@gmail.com으로 연락 바랍니다.