공부하는 책인 Do it! C언어 입문(김성엽)에서 콜백 함수를 쉽게 잘 풀어서 설명해주는 것 같아 일부를 인용하여 게시글을 작성한다.
C언어 공부를 통해 기초를 다지거나 더 자세히 보고싶다면 책을 사서 보길 추천한다.
완제품 형식의 프로그램을 만드는 프로그래머도 있는 반면에 음성 데이터를 압축하거나 영상 데이터를 변환하는 등 특별한 지식이 필요한 기능을 쉽게 사용할 수 있도록 함수로 만들어서 판매하는 프로그래머들도 있다.
이러한 프로그래머들을 라이브러리 프로그래머라고 한다.
이들은 자신의 코드가 노출되면 안 되기 때문에 코드를 컴파일해 라이브러리(library, *.lib) 형식의 파일로 제공하고, 라이브러리 안의 함수들이 어떤 형태로 선언된 함수인지 알아야 코드를 자세히 볼 수 없는 사용자들이 사용할 수 있기에 함수의 원형들을 헤더(header, *.h) 파일에 적어서 함께 제공한다.
예시를 들어 설명하자면, 두 정수의 합을 구하는 Sum 함수를 포함하는 라이브러리에서
사용자가 라이브러리 파일을 볼 수는 없겠지만 파일 안에는 아래와 같은 형식의 코드가 있을 것이다.
int Sum(int a, int b){
return a+b;
}
헤더에는 아래처럼 함수의 원형 형태로 저장되어 있을 것이다.
int Sum(int a, int b);
그렇다면 사용자는 헤더를 참고하여 아래의 형태처럼 Sum 함수를 사용할 수 있을 것이다.
#include "sum.h"
#pragma comment(lib, "sum.lib")
void main(){
int result = Sum(2, 3);
}
이런 식으로 라이브러리가 활용된다.
여기서 만약 라이브러리의 사용자들이 Sum 함수에 전달되는 두 숫자 값이 음수일시에 양수로 변환해서 합산하는 함수도 추가로 만들어달라고 요청한다면, 라이브러리 프로그래머는 아래와 같이 새로운 기능이 추가된 Sum_absolute 함수를 라이브러리에 추가해야할 것이다.
int Sum(int a, int b){
return a+b;
}
int Sum_absolute(int a, int b){
if(a<0) a *= (-1); //절대값 구하기
if(b<0) b *= (-1);
return a+b;
}
여기서 이러한 불편함을 없애고자 사용자가 라이브러리의 코드를 보지 않으면서도 매개변수를 통해 함수에 기능을 추가할 수 있도록 라이브러리 프로그래머가 미리 설계한다.
int Sum(int a, int b, void (*pa)(int *), void (*pb)(int *)){
if(NULL != pa) (*pa)(&a);
if(NULL != pb) (*pb)(&b);
return a+b;
}
그렇다면 헤더 파일은 아래와 같을 것이다.
int Sum(int a, int b, void(*pa)(int *), void (*pb)(int *));
이를 마주한 라이브러리 사용자는 아래와 같은 코드를 통해 이 라이브러리를 이용할 수 있을 것이다.
#include "sum.h"
#pragma comment(lib, "sum.lib")
void Absolute(int *p){
if(*p<0) *p = (*p) * (-1)
}
void main(){
int result = Sum(-2, 3, Absolute, NULL); //5
int result = Sum(2, -3, Absolute, NULL); //-1
int result = Sum(-2, -3, Absolute, Absolute); //5
}
여기서 Absolute()의 주소값이 포인터 변수 pa에 들어가면서 라이브러리에서 *pa가 a의 주소값을 받아 실행되며 *pa가 뜻하는 Absolute(a)가 실행되게 되는 것이다.
이해가 안 간다면 이전 글에서 함수 포인터에 대해 공부하고 오길 바란다.
이처럼 자신이 사용할 함수가 명시적으로 호출되지않고 함수 포인터에 의해서 호출되는 방식을 '콜백'이라고 부른다.
그리고 호출되는 함수(Absolute())를 콜백 함수라고 부른다.
이제 다 이해했다고 가정한 상태에서 쉽게 요약하자면 많은 사용자들의 다양한 조건을 미리 예측해 반영하면 좋겠지만, 조건문이 많아지는 비효율적인 함수가 되는것을 막기위해 함수 포인터를 사용하여 라이브러리 사용자들에게 조건이나 요구를 스스로 반영할 수 있도록 도움을 요청하는 것이다.
이 방식을 사용해 여러 문제나 나중에 추가될 기술과 같은 변화에 대비할 수 있고, 라이브러리가 노출되지 않고도 사용자에게 사용성을 높여줄 수 있다.
막상 이해하고 나면 크게 어려운 개념은 아니지만 이를 설명하는건 정말 어려운 것 같다.
어렵게 공부해온 포인터가 점점 많은 기능을 수행하는 걸 보니 C언어를 공부한 보람이 있는 것 같다.