📝 C 언어 함수 포인터와 콜백 함수: 유연한 코드 설계를 위한 핵심 패턴
C언어C 언어에서 함수를 변수처럼 다루는 '함수 포인터'와 이를 활용한 '콜백 함수'의 모든 것을 파헤칩니다. 코드의 유연성을 극대화하고, 재사용 가능한 모듈을 설계하는 핵심 비법을 실제 예제와 함께 자세히 설명합니다. (키워드: C언어, 함수 포인터, 콜백 함수, 디자인 패턴, 코드 재사용성, 고차 함수)

1. 서론: 함수, 그 이상의 활용 - C언어의 유연성을 깨우다
C 언어는 강력하고 효율적인 프로그래밍 언어로 널리 사용되지만, 때로는 그 절차지향적 특성 때문에 코드의 유연성이 부족하다고 느끼는 경우가 있습니다. 하지만 C 언어에도 함수를 마치 변수처럼 다루고, 필요할 때 원하는 함수를 실행시키는 강력한 메커니즘이 존재합니다. 바로 **'함수 포인터(Function Pointer)'**와 이를 활용한 **'콜백 함수(Callback Function)'**입니다.
이번 글에서는 C 언어의 함수 포인터가 무엇인지, 어떻게 선언하고 사용하는지, 그리고 이 함수 포인터가 실제 프로그래밍에서 '콜백 함수'라는 강력한 디자인 패턴으로 어떻게 활용되는지 자세히 살펴보겠습니다. 이를 통해 여러분의 C 언어 코드를 더욱 유연하고 재사용 가능하게 만드는 핵심 비법을 깨우치게 될 것입니다.
2. 함수 포인터: 함수를 가리키는 포인터
우리가 변수의 주소를 저장하기 위해 포인터 변수를 사용하듯이, 함수 포인터는 함수의 메모리 주소를 저장하는 포인터입니다. 이를 통해 우리는 실행 시점에 어떤 함수를 호출할지 동적으로 결정할 수 있게 됩니다.
2.1. 함수 포인터 선언 방법
함수 포인터를 선언하는 문법은 일반 변수 포인터보다 조금 복잡해 보일 수 있습니다. 핵심은 '반환형 (*포인터변수이름)(매개변수 타입, ...)' 형태를 기억하는 것입니다.
// 1. 반환형이 int이고 매개변수가 int 두 개인 함수의 포인터 선언
int (*ptr_to_func)(int, int);
// 예시 함수
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
ptr_to_func = &add; // add 함수의 주소를 함수 포인터에 할당
// 또는 ptr_to_func = add; (C에서는 &를 생략해도 됩니다)
int result = ptr_to_func(10, 5); // 함수 포인터를 통해 add 함수 호출
printf("Add result: %d\n", result); // 출력: Add result: 15
ptr_to_func = subtract; // subtract 함수의 주소를 할당
result = ptr_to_func(10, 5); // 함수 포인터를 통해 subtract 함수 호출
printf("Subtract result: %d\n", result); // 출력: Subtract result: 5
return 0;
}
2.2. typedef를 이용한 간결한 선언
함수 포인터 선언이 복잡하게 느껴진다면, typedef를 사용하여 별칭을 부여하면 훨씬 간결하고 가독성 높게 코드를 작성할 수 있습니다.
// int를 반환하고 int 두 개를 매개변수로 받는 함수 포인터 타입 정의
typedef int (*OperationFunc)(int, int);
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int main() {
OperationFunc op_ptr; // typedef로 정의된 타입 사용
op_ptr = add;
printf("Add result: %d\n", op_ptr(20, 10)); // 출력: Add result: 30
op_ptr = subtract;
printf("Subtract result: %d\n", op_ptr(20, 10)); // 출력: Subtract result: 10
return 0;
}
3. 콜백 함수: 유연한 코드 설계를 위한 핵심 패턴
이제 함수 포인터의 진정한 가치를 발휘하는 **'콜백 함수(Callback Function)'**에 대해 알아보겠습니다. 콜백 함수는 특정 이벤트가 발생하거나, 특정 작업이 완료되었을 때 시스템이나 다른 함수가 호출하도록 등록해 둔 함수를 의미합니다. 즉, "나중에 호출해줘!" 하고 맡겨두는 함수입니다.
3.1. 콜백 함수의 개념과 동작 원리
콜백 함수는 다음과 같은 방식으로 작동합니다.
- 호출 함수(Caller Function): 어떤 작업을 수행하는 함수가 있습니다. 이 함수는 작업 중간 또는 완료 시점에 특정 기능을 수행해야 하는데, 그 기능이 고정되어 있지 않고 외부에서 주입되기를 기대합니다.
- 콜백 함수 (Callback Function): 호출 함수가 나중에 실행할 '특정 기능'을 정의한 함수입니다. 이 함수의 주소를 호출 함수에게 전달합니다.
- 함수 포인터 (The Bridge): 호출 함수는 전달받은 콜백 함수의 주소를 함수 포인터에 저장하고 있다가, 필요한 시점에 이 포인터를 통해 콜백 함수를 호출합니다.
이러한 구조를 통해 호출 함수는 **'어떤 기능'을 실행할지 몰라도 '기능을 실행해야 할 시점'**만 알면 됩니다. 이는 코드의 모듈화와 재사용성을 극대화합니다.
3.2. 콜백 함수 실제 예제: 배열 정렬 (버블 정렬)
가장 대표적인 콜백 함수의 예는 정렬 함수입니다. qsort()와 같은 표준 라이브러리 함수가 대표적이지만, 여기서는 직접 간단한 버블 정렬 함수를 만들면서 콜백의 위력을 보여드리겠습니다.
우리는 **'오름차순'**으로 정렬할 수도 있고, **'내림차순'**으로 정렬할 수도 있습니다. 이 **'정렬 기준'**을 콜백 함수로 전달하여 버블 정렬 함수를 유연하게 만들어 봅시다.
#include <stdio.h>
// int 두 개를 비교하여 int 값을 반환하는 함수 포인터 타입 정의
// 반환값이 음수: a < b
// 반환값이 양수: a > b
// 반환값이 0: a == b
typedef int (*CompareFunc)(int a, int b);
// 1. 오름차순 비교 함수 (a가 b보다 작으면 음수 반환)
int compare_asc(int a, int b) {
return a - b;
}
// 2. 내림차순 비교 함수 (a가 b보다 크면 음수 반환)
int compare_desc(int a, int b) {
return b - a; // 순서를 바꿔서 빼면 내림차순
}
// 3. 콜백 함수를 사용하는 범용 버블 정렬 함수
// 배열, 배열 크기, 비교 함수 포인터를 매개변수로 받음
void bubble_sort(int arr[], int size, CompareFunc comparer) {
int i, j, temp;
for (i = 0; i < size - 1; i++) {
for (j = 0; j < size - 1 - i; j++) {
// 전달받은 comparer 함수를 사용하여 두 요소를 비교
if (comparer(arr[j], arr[j+1]) > 0) { // arr[j]가 arr[j+1]보다 크다면 (정렬 기준에 따라)
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
// 배열 출력 함수
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Original array: ");
print_array(arr, size);
// 오름차순으로 정렬
bubble_sort(arr, size, compare_asc);
printf("Sorted (Ascending): ");
print_array(arr, size); // 출력: 11 12 22 25 34 64 90
// 배열을 다시 초기화하고 내림차순으로 정렬
int arr_desc[] = {64, 34, 25, 12, 22, 11, 90};
bubble_sort(arr_desc, size, compare_desc);
printf("Sorted (Descending): ");
print_array(arr_desc, size); // 출력: 90 64 34 25 22 12 11
return 0;
}
위 예제에서 bubble_sort 함수는 compare_asc와 compare_desc 함수를 콜백 함수로 받아서, 동일한 정렬 로직으로 오름차순/내림차순 정렬을 모두 수행합니다. bubble_sort는 **'무엇을 비교할지'**에 대해서는 전혀 모르고 **'비교하는 방법(콜백)'**만 전달받아 유연하게 동작합니다.
4. 콜백 함수의 다양한 활용 분야
콜백 함수는 위 예제 외에도 C 언어 프로그래밍의 다양한 곳에서 활용됩니다.
- 이벤트 처리: GUI 프로그래밍에서 버튼 클릭, 키보드 입력 등 특정 이벤트 발생 시 호출될 함수를 등록할 때 (예: glutDisplayFunc in OpenGL)
- 비동기 작업: 네트워크 통신이나 파일 입출력처럼 시간이 오래 걸리는 작업이 완료되었을 때 결과를 처리할 함수를 등록할 때
- 제네릭 라이브러리: 정렬(qsort), 탐색(bsearch)과 같은 범용 라이브러리에서 사용자가 정의한 비교 함수를 콜백으로 받아 다양한 데이터 타입에 적용할 때
- 하드웨어 제어: 임베디드 시스템에서 특정 인터럽트 발생 시 처리할 함수를 등록할 때
5. 결론: 함수 포인터와 콜백, C언어의 숨겨진 힘
C 언어의 함수 포인터와 콜백 함수는 언어가 제공하는 강력한 유연성과 확장성을 보여주는 핵심 개념입니다. 이는 단순한 문법을 넘어, 코드를 더욱 모듈화하고 재사용성을 높이며, 다양한 시나리오에 동적으로 대응할 수 있도록 하는 강력한 디자인 패턴입니다.
처음에는 문법이 다소 어렵게 느껴질 수 있지만, 이번 글의 예제를 통해 그 원리와 활용법을 충분히 익히셨기를 바랍니다. 함수 포인터와 콜백 함수를 자유자재로 다루게 된다면, 여러분의 C 언어 프로그래밍 실력은 한 단계 더 도약할 것입니다.
[🔔 다음 글 예고: C언어 데이터 구조의 꽃, 링크드 리스트 완전 정복!] 다음 글에서는 함수 포인터와 더불어 C 언어의 핵심인 데이터 구조 중 가장 기본적이면서도 강력한 **'링크드 리스트(Linked List)'**에 대해 깊이 파고들 예정입니다. 동적인 데이터 관리가 필요한 여러분에게 필수적인 지식이 될 것입니다. 기대해주세요!
'C언어' 카테고리의 다른 글
| 현장에서 가장 많이 터지는 C 사고 패턴들 (0) | 2025.12.26 |
|---|---|
| C에서 아직도 사고 나는 이유는 문법이 아니다 (0) | 2025.12.25 |
| 이중 포인터로 2차원 배열 다루기 – 개념부터 실전 사용까지 (0) | 2025.11.20 |
| C 언어 포인터 완벽 정리 – 이 글 하나로 끝내기 (0) | 2025.11.19 |
| C언어 포인터, 동적할당, 메모리 관리 (0) | 2024.10.15 |