📝 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언어' 카테고리의 다른 글
| 이중 포인터로 2차원 배열 다루기 – 개념부터 실전 사용까지 (0) | 2025.11.20 |
|---|---|
| C 언어 포인터 완벽 정리 – 이 글 하나로 끝내기 (0) | 2025.11.19 |
| C언어 포인터, 동적할당, 메모리 관리 (0) | 2024.10.15 |
| C언어 구조체란? (3) | 2024.10.14 |
| 자료구조 연결리스트 개념, 장점, 단점, 배열과의 차이 (1) | 2023.06.04 |







