Call-Back Function
> 함수포인터의 구현.
변수 포인터가 참조할 변수와 자료형이 일치해야 하듯, 함수 포인터도 참조할 함수와
자료형이 일치해야 한다.
일반적으로 함수들은 내가 원하는 시점에 호출한다.
그러나 때로는 interrupt가 들어와서 내가 원하지 않는 타이밍에 함수가 호출될 수도 있다.
이때 호출되어지는 함수를 콜백함수라고 한다.
예를 들어 폰으로 게임을 하고 있다.
이 게임은 내가 호출한 것이다.
그런데 갑자기 전화가 오면서 게임 화면을 가려버렸다.
이 전화는 내가 의도한 것이 아니라 신호에 의해 OS가 스스로 실행한 것이다.
이 때의 전화가 콜백함수이다.
#include <stdio.h>
int sum(int a, int b)
{
return a + b;
}
int main(void)
{
int(*ptr)(int, int);
ptr = sum;
printf("%d", ptr(1, 2));
return 0;
}
> 콜백함수 구현
위의 예와 같이 콜백함수를 가장 흔하게 볼수 있는 곳은 Application과 OS간 호출이다.
Application에 콜백함수를 구현하고 OS에 콜백함수를 호출하는 함수를 구현한다.
그리고 OS에서 특정 이벤트 발생 시 콜백함수가 호출되어지낟.
콜백함수는 함수포인터를 이용해서 구현된다.
#include <stdio.h>
void UserFunc()
{
printf("A");
}
void SystemFunc(void(*fp)())
{
if (1)
{
fp();
}
}
int main(void)
{
SystemFunc(UserFunc);
return 0;
}
UserFunc는 Application 에 해당하고
SystemFunc는 OS에 해당한다.
SystemFunc가 UserFunc를 호출하기 위해서 함수포인터가 사용되었다.
Definition:
호출될 함수를 알려주어, 다른 프로그램 또는 다른 모듈에서 함수를 호출하게 하는 방법.
일반적으로 OS가 호출할 어플리케이션의 함수를 지정해 특정한 사건 또는 메시지가 발생했을 때
호출되도록 지정할 수 있다. 이런 함수를 Callback 함수라고 한다.
더 자세히 설명해보면 일반적인 함수는 클라이언트 측에서
서버의 함수를 호출하여 사용하는 반면에 콜백함수는 일반적인 함수 호출과는 다르게
함수 포인터(or 람다 함수 등)을 이용해 콜백 함수를 서버 측으로 전달한다.
이후 서버 측에는 콜백함수를 바로 사용할 수도 있으며, 콜백함수를 등록한 이후에도 재사용이 가능하다.
(내가 호출하는 것이 아닌 서버 측에 의해서 호출됨)
이를 이용한 대표적인 예가 GUI Event & Handler 이다.
특정 이벤트가 발생했을 때 그에 맞는 처리가 되도록 핸들러를 등록하게 되면
서버 측에서 특정 이벤트가 발생했을 때 핸들러를 호출하여 처리하는 Sequence 이다.
#include <iostream>
using namespace std;
/* Server */
typedef void(*CALLBACK_FUNC)(int); //define Function pointer
CALLBACK_FUNC cbf = NULL; // Global variable for handling with callBack function
void RegistCallback(CALLBACK_FUNC cb) // CallBack Register Function
{
cbf = cb;
}
void StartCallback()
{
if(cbf == NULL)
{
cout << "Callback Function is not Registed" << endl;
return;
}
cout << "Server Calls CB." << endl;
cbf(1); // Calling Callback function in registred Server / transmit 1 as arg.
}
/* Client */
void UserCallback(int n)
{
if(n == 1)
{
cout << "True" << endl;
}
else
{
cout << "False" << endl;
}
}
int main(void)
{
/* Client */
RegistCallback(UserCallback); // Regist Callback function
/* Server */
StartCallback();
return 0;
}
컴퓨터에 있는 프로그램을 실행하면 프로그램이 먼저 메모리에 올라온다.
즉, 변수 뿐만 아니라 프로그램도 메모리를 차지한다.
당연한 이야기이지만 함수도 시작 주소가 있을 것이다.
포인터가 변수의 메모리 주소를 갖는다면, 함수 주소도 역시 포인터로 받을 수 있다.
변수의 주소가 아닌 함수의 시작 주소를 담은 포인터 변수를 통해 호출하는 함수를 포인터 함수라고 한다.
함수 이름을 직접 호출하는 것이 아니라 함수의 주소를 가지고 있는 포인터 변수로 호출하는 것이며,
포인터 변수는 고정이 아니라 코딩에 따라 여러 함수의 주소로 바꿀 수 있다.
즉, 함수 이름을 직접 부르는 것을 정적 호출이라고 한다면, 포인터 변수를 통하는 방법을 동적 호출이라고 한다.
함수 이름으로 실행하면 될 것을 왜 굳이 포인터 변수를 이용하는 이유가 무엇일까?
>> C 언어 콜백함수 구현
포인터 함수로 콜백함수(Callback)를 구현할 수 있다.
콜백함수는 인수로 넘겨받은 함수를 말함
일반적으로 함수는 숫자나 문자 같은 데이터를 받다 보니 인수로 함수를 받는다고 하면 낮설겠지만,
변수가 숫자 , 문자 같은 데이터를 넘겨준다고 한다면, 콜백함수는 특정 기능을 넘기는 것이다.
즉, 어떤 조건이 발생하면 인수로 넘긴 콜백함수를 실행해 달라는 것으로
함수를 호출하는 쪽에서 어떤 조건이 발생하면 처리해야 할 내용을 미리 작성해서 콜백함수로 넘겨주는 것이다.
콜백함수를 사용하면 라이브러리와 같은 기존 코드를 수정하지 않으면서도 다양한 기능을 실행할 수 있다.
예를 들어 정렬 함수를 만들어서 라이브러리로 제공하려고 한다.
#include <stdio.h>
void sort(int* data, int n)
{
int* ptr;
int idx;
int tmp;
int is_done;
while (1)
{
ptr = data;
is_done = 1;
for (idx = 0; idx < n - 1; idx++)
{
if (*ptr > *(ptr + 1))
{
tmp = *ptr;
*ptr = *(ptr + 1);
*(ptr + 1) = tmp;
is_done = 0;
}
ptr++;
}
if (is_done)
{
break;
}
}
}
int main(void)
{
int arr[10] = { 4, 5, 1, 2, 6, 7, 2, 0, 9, 8 };
int ndx;
sort(arr, 10);
for (ndx = 0; ndx < 10; ndx++)
{
printf("%d\n", arr[ndx]);
}
return 0;
}
정렬을 위해 두 개의 변수 값을 비교하고 결과에 따라 변수 값을 교환하는 코드를 넣어서
sort() 함수를 작성하고 라이브러리로 만듬
그래서 일이 끝났다고 좋아하며 다른 개발자에게 해당 코드를 제공한다.
그런데 그 개발자가 올림차순이 아니라 내림차순이 필요하다며 수정하거나
아니면 내림차순으로 정렬하는 함수를 추가해 달라고 한다.
이런 경우 내림차순 함수를 추가하기보다는 변수 값 비교와 치환하는 부분을 라이브러리를 사용하는
개발자가 작성할 수 있도록 제공하는 것이 좋다.
즉, 라이브러리르를 쓸 사람이 마음대로 활용하라는 것이다.
이렇게 하려면 라이브러리 안에서 개발자가 작서앟ㄴ 비교, 치환하는 함수를 호출해야 하는데
그 개발자가 무슨 이름으로 함수를 만들지 어떻게 알까?
어쩔 수 없이 개발자에게 sort_user() 이름으로 꼭 만들라고 부탁해야 할까?
이 방법이 가능하다고 해도 sort함수는 무조건 sort_user()만 호출하게 될 텐데 제약이 너무 크다.
개발자가 만든 sort_user()는 내림차순 코드만 넣었는데 나중에 다른 루틴에서 올림도 필요하다고
가정한다면 어쩔 수 없이 새로 함수를 추가해야 할 것이다.
간단한 예를 들기 위해 정렬 함수를 꺼내서 경우의 수가 올림, 내림 두가지이지만,
정렬 함수가 아니라 더 다양한 처리를 해야 한다면 이런 방법의 코딩은 너무 지저분하다.
라이브러리 함수라고 얘기하기도 부끄러울 정도이다.
이럴 때 콜백함수를 사용해야 한다.
라이브러리에 있는 정렬 함수를 호출할 때 데이터만 보내는 것이 아니라
비교, 치환하는 함수를 함께 인수로 던지는 것이다.
이렇게 콜백함수로 라이브러리를 만들면 외부 요청에 의해 수정하는 일이 크게 줄어든다.
#include <stdio.h>
/* Library Function */
void sort(int* data, int n, int(*call_back)(int*, int*))
{
int* ptr;
int idx;
int tmp;
int is_done;
while (1)
{
ptr = data;
is_done = 1;
for (idx = 0; idx < n - 1; idx++)
{
//if (*ptr > *(ptr + 1))
if (!call_back(ptr, ptr + 1)) // if swap,
{
is_done = 0;
}
ptr++;
}
if (is_done)
{
break;
}
}
}
/* Developer Code */
int sort_descending(int* data1, int* data2)
{
int tmp;
if (*data1 < *data2)
{
tmp = *data1;
*data1 = *data2;
*data2 = tmp;
return 0; // Swaped
}
return 1; // Not Swaped
}
int main(void)
{
int arr[10] = { 4, 5, 1, 2, 6, 7, 2, 0, 9, 8 };
int ndx;
sort(arr, 10, sort_descending);
for (ndx = 0; ndx < 10; ndx++)
{
printf("%d\n", arr[ndx]);
}
return 0;
}
라이브러리 제공자는 sort() 함수만 만들고 개발자는 정렬 함수까지 넣어서 호출한다.
위 예제에서는 내림차순 함수를 만들어서 호출하였다.
데이터와 함수의 짝짓기
C++ 객체화의 가장 큰 특징은 변수와 함수를 한 몸으로 묶을 수 있다는 것이다.
객체를 선언할 때 접근 순위를 두어서 그 객체의 변수를 그 객체에 포함된 함수로만 다룰 수 있다.
C 언어는 특정 변수를 특정 함수에서만 처리하는 것은 오로지 프로그래머의 재량에 달려 있다.
데이터에 따라 실행하려는 함수를 다르게 하려면 if 절을 이용해야 한다.
C언어에서 포인터 함수에 구조체를 함께 사용하면 변수와 실행함수를 묶어서 인수로 넘길 수 있다.
C++ 처럼 강력한 접근 제한자가 없지만, 데이터에 따라 처리하는 함수를 묶을 수 있고 다른 함수에 인수로 넘길 수도 있다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FUN_LENGTH 0
#define FUN_TEXT 1
void fun1(char* ptr)
{
printf("문자열 길이 = %ld\n", strlen(ptr));
}
void fun2(char* ptr)
{
printf("문자열 내용 = %s\n", ptr);
}
void fun(char* ptr, int fun_num)
{
if (fun_num == FUN_LENGTH)
{
fun1(ptr);
}
else
{
fun2(ptr);
}
}
int main(void)
{
fun("길이", FUN_LENGTH);
fun("내용", FUN_TEXT);
return 0;
}
#include <stdio.h>
#include <string.h>
typedef struct _obj
{
void(*function)(char* ptr);
char* data;
} object_t;
void fun1(char* ptr)
{
printf("Stirng length : %ld\n", strlen(ptr));
}
void fun2(char* ptr)
{
printf("Stirng content: %s\n", ptr);
}
void call_fun(object_t* ptr)
{
ptr->function(ptr->data);
}
int main(void)
{
object_t obj1 = { fun1, "Length?" };
object_t obj2 = { fun2, "Content?" };
call_fun(&obj1);
call_fun(&obj2);
return 0;
}
'Data Structure & Algorithm' 카테고리의 다른 글
Toggle (0) | 2022.11.25 |
---|---|
Coin (0) | 2022.11.21 |
Sort Score (0) | 2022.11.18 |
Flower, Chicken, Cards (0) | 2022.11.14 |
[Sort]Bubble, Selection, Insertion (0) | 2022.10.31 |