Quick Syntax
함수 포인터는 정확한 인자·반환형을 가진 호출 가능한 주소입니다. 동작을 주입하면 callback, 같은 시그니처를 인덱스로 고르면 dispatch table이 됩니다.
#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int (*fp)(int, int) = add; /* 함수 하나를 가리키는 선언 */
printf("%d\n", fp(2, 3));
typedef int (*Op)(int, int); /* 같은 시그니처에 이름을 붙인다. */
Op ops[] = { add, sub };
size_t choice = 0;
if (choice < sizeof ops / sizeof ops[0] && ops[choice] != NULL) {
printf("%d\n", ops[choice](8, 3));
}함수 포인터는 object pointer와 바꿔 쓸 수 없습니다. dispatch table에서는 인덱스 범위와 NULL 여부를 호출 전에 확인합니다.
문법
int (*fp)(int, int)는 "정수 두 개를 받아 정수를 반환하는 함수의 포인터"입니다.
fp는 이름(*fp)는 포인터라는 뜻(int, int)는 인자 목록- 맨 앞
int는 반환형
선언이 복잡해지면 typedef로 감싸는 편이 훨씬 읽기 쉽습니다.
typedef int (*BinaryOp)(int, int);함수 이름은 대부분의 식에서 함수 포인터로 변환되므로 add와 &add 모두 초기화에 쓸 수 있습니다. 다만 함수 포인터와 void * 같은 객체 포인터를 서로 일반 변환 도구처럼 섞지는 않습니다.
사용 예시
함수 포인터의 가장 흔한 용도는 콜백입니다.
typedef void (*Logger)(const char *msg);
void log_stdout(const char *msg) {
printf("[LOG] %s\n", msg);
}
void process(int *data, int n, Logger logger) {
for (int i = 0; i < n; i++) {
if (data[i] < 0 && logger != NULL) {
logger("negative value");
}
}
}같은 시그니처의 함수를 목록으로 모아 dispatch table처럼 쓸 수도 있습니다.
typedef int (*BinaryOp)(int, int);
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; }
BinaryOp ops[] = {add, sub, mul};
printf("%d\n", ops[1](10, 3));table에서 고른 index와 함수 포인터는 호출 전에 함께 검사합니다. 외부 command 값을 배열 index로 바로 쓰면 범위 밖 호출로 이어질 수 있습니다.
if (op_index < sizeof(ops) / sizeof(ops[0]) && ops[op_index] != NULL) {
result = ops[op_index](left, right);
}빠른 점검
- 선언이 복잡하면
typedef로 감싸는 편이 읽기 쉽습니다. - 알고리즘은 고정하고 행동만 바꾸고 싶으면 콜백이 잘 맞습니다.
- 같은 시그니처의 연산 여러 개를 선택하는 구조면 dispatch table이 자연스럽습니다.
- 호출 전에는
NULL여부를 확인하는 편이 안전합니다. qsort는 함수 포인터 API를 이해할 때 가장 대표적인 표준 라이브러리 예시입니다.
주의할 점
함수 포인터는 시그니처가 정확히 맞아야 합니다. 반환형이나 인자 타입이 다른 함수를 억지로 캐스팅해서 넘기면 호출 시점에 정의되지 않은 동작으로 이어질 수 있습니다.
참고 링크
2 sources