C에서 "콜백"이란 무엇이며 어떻게 구현됩니까?


153

내가 읽은 독서에서 Core Audio는 콜백 (및 C ++에 크게 의존하지만 다른 이야기입니다).

작업을 수행하기 위해 다른 함수에서 반복적으로 호출하는 함수를 설정하는 개념 (일종)을 이해합니다. 나는 그들이 어떻게 설정되고 실제로 어떻게 작동하는지 이해하지 못합니다. 모든 예가 이해 될 것이다.

답변:


203

C에는 "콜백"이 없으며 다른 일반적인 프로그래밍 개념을 넘지 않아야합니다.

함수 포인터를 사용하여 구현됩니다. 예를 들면 다음과 같습니다.

void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}

int getNextRandomValue(void)
{
    return rand();
}

int main(void)
{
    int myarray[10];
    populate_array(myarray, 10, getNextRandomValue);
    ...
}

여기서 populate_array함수는 함수 포인터를 세 번째 매개 변수로 사용하여 배열을 채울 값을 가져 오도록 호출합니다. getNextRandomValue임의의 값을 반환하고에 대한 포인터를 전달하는 콜백을 작성 했습니다 populate_array. populate_array콜백 함수를 10 번 호출하고 주어진 배열의 요소에 반환 된 값을 할당합니다.


2
여기에 잘못되었을 수도 있지만 함수 포인터를 호출하는 populate_array의 행은 다음과 같습니다. array [i] = (* getNextValue) (); ?
Nathan Fellman

40
역 참조 연산자는 addressof 연산자와 마찬가지로 함수 포인터와 함께 선택 사항입니다. myfunc (...) = (* myfunc) (...) 및 & myfunc = myfunc
aib

1
@NathanFellman 방금 Expert C Programming을 읽고 함수 포인터가 잘 호출되는 것을 설명합니다.
매트 클라크 슨

1
@johnny 표준이 그렇게 말했기 때문입니다. 공감 된 의견을보십시오.
aib

3
@Patrick : populateArray는 라이브러리에 있으며 12 년 전에 작성되었으며 getNextRandomValue를 직접 작성했습니다 (어제). 직접 호출 할 수 없습니다. 비교기를 직접 제공하는 라이브러리 정렬 기능을 생각하십시오.
aib

121

다음은 C에서의 콜백 예입니다.

이벤트가 발생할 때 콜백을 등록 할 수있는 코드를 작성하려고한다고 가정 해 봅시다.

먼저 콜백에 사용되는 함수 유형을 정의하십시오.

typedef void (*event_cb_t)(const struct event *evt, void *userdata);

이제 콜백을 등록하는 데 사용되는 함수를 정의하십시오.

int event_cb_register(event_cb_t cb, void *userdata);

콜백을 등록하는 코드는 다음과 같습니다.

static void my_event_cb(const struct event *evt, void *data)
{
    /* do stuff and things with the event */
}

...
   event_cb_register(my_event_cb, &my_custom_data);
...

이벤트 디스패처 내부에서 콜백은 다음과 같은 구조체에 저장 될 수 있습니다.

struct event_cb {
    event_cb_t cb;
    void *data;
};

이것이 콜백을 실행하는 코드의 모습입니다.

struct event_cb *callback;

...

/* Get the event_cb that you want to execute */

callback->cb(event, callback->data);

내가 필요한 것만 사용자 데이터 부분은 사용자가 콜백 기능에 필요한 사용자 지정 데이터 (예 : 장치 핸들)를 전달하려는 경우 매우 유용합니다.
uceumern

확인 질문 : 콜백 typedef는 함수 주소에 대한 포인터이기 때문에 별표가 있습니까? 별표가 없으면 정확하지 않습니까? 그것이 틀렸다
twildeman

@twildeman 경고가 설정된 표준 C 모드에서 컴파일하여 자신의 질문에 대답하는 것은 사소한 것 같습니다. 최소화 된 테스트 프로그램을 작성할 수도 있습니다. 와 같은 코드 libsrtp는 경고 를 제공하지 않습니다. 그런 다음 그러한 유형이 함수 인수로 나타날 때 배열이 첫 번째 요소에 대한 포인터로 붕괴되는 것처럼 포인터 대 함수로 '부패'해야하므로 결국 동일한 결과가 발생한다고 가정합니다. 어느 쪽이든. 그러나 내가 찾은 그러한 typedef에 대한 논의는이 측면을 한 눈에 보지 않고 프로토 타입이나 포인터를 선언하는 데 중점을 둔다 는 점이 흥미 롭습니다.
underscore_d

이것이 무엇인지 전혀 몰라서 성공적으로 컴파일 할 수 없습니다. 누구나 성공적으로 컴파일하기 위해 자세한 방법으로 설명하거나 나머지 코드를 채울 수 있습니까?
앤디 린

20

간단한 콜백 프로그램. 그것이 당신의 질문에 대답하기를 바랍니다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "../../common_typedef.h"

typedef void (*call_back) (S32, S32);

void test_call_back(S32 a, S32 b)
{
    printf("In call back function, a:%d \t b:%d \n", a, b);
}

void call_callback_func(call_back back)
{
    S32 a = 5;
    S32 b = 7;

    back(a, b);
}

S32 main(S32 argc, S8 *argv[])
{
    S32 ret = SUCCESS;

    call_back back;

    back = test_call_back;

    call_callback_func(back);

    return ret;
}

9

C의 콜백 함수는 다른 함수 내에서 사용되도록 지정된 함수 매개 변수 / 변수와 같습니다. 위키 예제

아래 코드에서

#include <stdio.h>
#include <stdlib.h>

/* The calling function takes a single callback as a parameter. */
void PrintTwoNumbers(int (*numberSource)(void)) {
    printf("%d and %d\n", numberSource(), numberSource());
}

/* A possible callback */
int overNineThousand(void) {
    return (rand() % 1000) + 9001;
}

/* Another possible callback. */
int meaningOfLife(void) {
    return 42;
}

/* Here we call PrintTwoNumbers() with three different callbacks. */
int main(void) {
    PrintTwoNumbers(&rand);
    PrintTwoNumbers(&overNineThousand);
    PrintTwoNumbers(&meaningOfLife);
    return 0;
}

함수 호출 PrintTwoNumbers 내의 함수 (* numberSource)는 코드가 실행되는대로 PrintTwoNumbers 내부에서 "콜백"/ 실행하는 함수입니다.

따라서 pthread 함수와 같은 것이 있다면 인스턴스화에서 루프 내에서 실행되도록 다른 함수를 지정할 수 있습니다.


6

C의 콜백은 다른 함수가 작업을 수행하는 시점에서 "콜백"하기 위해 다른 함수에 제공되는 함수입니다.

콜백이 사용되는 두 가지 방법 이 있습니다 동기식 콜백과 비동기식 콜백의 있습니다. 다른 작업에 동기식 콜백이 제공되어 일부 작업을 수행 한 다음 작업이 완료된 상태로 호출자에게 반환됩니다. 작업을 시작한 다음 작업이 완료되지 않은 상태로 호출자에게 반환하는 다른 함수에 비동기 콜백이 제공됩니다.

동기식 콜백은 일반적으로 다른 함수가 작업의 일부 단계를 위임하는 다른 함수에 대리자를 제공하는 데 사용됩니다. 이 위임의 전형적인 예는 함수 bsearch()qsort()C 표준 라이브러리입니다. 이 두 함수는 함수가 제공하는 작업 중에 사용되는 콜백을 사용하여의 경우 검색되는 데이터의 유형 bsearch()또는의 경우 정렬 된 경우 qsort()해당 함수에 의해 알 필요가 없습니다. 익숙한.

예를 들어, 여기에는 bsearch()다른 비교 함수, 동기식 콜백 을 사용 하는 작은 샘플 프로그램이 있습니다. 콜백 함수에 데이터 비교를 위임함으로써이 함수를 bsearch()사용하면 런타임에 사용할 비교 유형을 결정할 수 있습니다. bsearch()함수가 리턴 할 때 태스크가 완료되었으므로 동기식 입니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int iValue;
    int kValue;
    char label[6];
} MyData;

int cmpMyData_iValue (MyData *item1, MyData *item2)
{
    if (item1->iValue < item2->iValue) return -1;
    if (item1->iValue > item2->iValue) return 1;
    return 0;
}

int cmpMyData_kValue (MyData *item1, MyData *item2)
{
    if (item1->kValue < item2->kValue) return -1;
    if (item1->kValue > item2->kValue) return 1;
    return 0;
}

int cmpMyData_label (MyData *item1, MyData *item2)
{
    return strcmp (item1->label, item2->label);
}

void bsearch_results (MyData *srch, MyData *found)
{
        if (found) {
            printf ("found - iValue = %d, kValue = %d, label = %s\n", found->iValue, found->kValue, found->label);
        } else {
            printf ("item not found, iValue = %d, kValue = %d, label = %s\n", srch->iValue, srch->kValue, srch->label);
        }
}

int main ()
{
    MyData dataList[256] = {0};

    {
        int i;
        for (i = 0; i < 20; i++) {
            dataList[i].iValue = i + 100;
            dataList[i].kValue = i + 1000;
            sprintf (dataList[i].label, "%2.2d", i + 10);
        }
    }

//  ... some code then we do a search
    {
        MyData srchItem = { 105, 1018, "13"};
        MyData *foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_iValue );

        bsearch_results (&srchItem, foundItem);

        foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_kValue );
        bsearch_results (&srchItem, foundItem);

        foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_label );
        bsearch_results (&srchItem, foundItem);
    }
}

비동기 콜백은 콜백을 제공하는 호출 된 함수가 리턴 할 때 태스크가 완료되지 않을 수 있다는 점에서 다릅니다. 이 유형의 콜백은 종종 I / O 작업이 시작된 비동기 I / O와 함께 사용되며 완료되면 콜백이 호출됩니다.

다음 프로그램에서 우리는 TCP 연결 요청을 수신하기위한 소켓을 만들고 요청이 수신되면 수신을 수행하는 함수는 제공된 콜백 함수를 호출합니다. 이 간단한 응용 프로그램은 telnet유틸리티 또는 웹 브라우저를 사용하여 다른 창에서 연결을 시도하는 동안 한 창에서 실행하여 실행할 수 있습니다 .

Microsoft accept()https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx 에서 제공하는 예제에서 대부분의 WinSock 코드를 들었습니다.

이 응용 프로그램을 시작 listen()당신이 중 하나를 사용할 수 있도록 포트 8282를 사용하여 로컬 호스트, 127.0.0.1에 telnet 127.0.0.1 8282또는 http://127.0.0.1:8282/.

이 샘플 응용 프로그램은 Visual Studio 2017 Community Edition을 사용하여 콘솔 응용 프로그램으로 만들어졌으며 Microsoft WinSock 버전의 소켓을 사용하고 있습니다. Linux 응용 프로그램의 경우 WinSock 기능을 Linux 대안으로 대체해야하며 Windows 스레드 라이브러리가 pthreads대신 사용 합니다.

#include <stdio.h>
#include <winsock2.h>
#include <stdlib.h>
#include <string.h>

#include <Windows.h>

// Need to link with Ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

// function for the thread we are going to start up with _beginthreadex().
// this function/thread will create a listen server waiting for a TCP
// connection request to come into the designated port.
// _stdcall modifier required by _beginthreadex().
int _stdcall ioThread(void (*pOutput)())
{
    //----------------------
    // Initialize Winsock.
    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR) {
        printf("WSAStartup failed with error: %ld\n", iResult);
        return 1;
    }
    //----------------------
    // Create a SOCKET for listening for
    // incoming connection requests.
    SOCKET ListenSocket;
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ListenSocket == INVALID_SOCKET) {
        wprintf(L"socket failed with error: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    //----------------------
    // The sockaddr_in structure specifies the address family,
    // IP address, and port for the socket that is being bound.
    struct sockaddr_in service;
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr("127.0.0.1");
    service.sin_port = htons(8282);

    if (bind(ListenSocket, (SOCKADDR *)& service, sizeof(service)) == SOCKET_ERROR) {
        printf("bind failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    //----------------------
    // Listen for incoming connection requests.
    // on the created socket
    if (listen(ListenSocket, 1) == SOCKET_ERROR) {
        printf("listen failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    //----------------------
    // Create a SOCKET for accepting incoming requests.
    SOCKET AcceptSocket;
    printf("Waiting for client to connect...\n");

    //----------------------
    // Accept the connection.
    AcceptSocket = accept(ListenSocket, NULL, NULL);
    if (AcceptSocket == INVALID_SOCKET) {
        printf("accept failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    else
        pOutput ();   // we have a connection request so do the callback

    // No longer need server socket
    closesocket(ListenSocket);

    WSACleanup();
    return 0;
}

// our callback which is invoked whenever a connection is made.
void printOut(void)
{
    printf("connection received.\n");
}

#include <process.h>

int main()
{
     // start up our listen server and provide a callback
    _beginthreadex(NULL, 0, ioThread, printOut, 0, NULL);
    // do other things while waiting for a connection. In this case
    // just sleep for a while.
    Sleep(30000);
}

동기식 및 비동기식 콜백을 모두 보여주는 탁월한 답변. C- * NIX에서 비동기 콜백을 사용하는 또 다른 구체적인 예는 비동기 신호 및 해당 신호 처리기입니다. 다음은 Linux에서 신호 처리기가 처리되는 방법에 대한 훌륭한 설명입니다 ( linkoverflow.com/questions/6949025/… ).
drlolly

4

C의 콜백은 일반적으로 함수 포인터와 관련 데이터 포인터를 사용하여 구현됩니다. 함수 on_event()및 데이터 포인터를 프레임 워크 함수에 전달합니다 watch_events()(예 :). 이벤트가 발생하면 데이터 및 일부 이벤트 특정 데이터로 함수가 호출됩니다.

콜백은 GUI 프로그래밍에도 사용됩니다. GTK + 튜토리얼 온 멋진 부분이 신호와 콜백의 이론을 .


2

위키 백과 기사 에는 C로 된 예제가 있습니다.

좋은 예는 Apache 웹 서버 레지스터에 함수 포인터를 전달하여 Apache 웹 서버 레지스터를 기능 보강하기 위해 작성된 새 모듈이 해당 함수를 다시 호출하여 웹 페이지 요청을 처리하는 것입니다.


0

일반적으로 이것은 함수의 메모리 위치를 가리키는 특수 변수 인 함수 포인터를 사용하여 수행 할 수 있습니다. 그런 다음이를 사용하여 특정 인수로 함수를 호출 할 수 있습니다. 따라서 콜백 함수를 설정하는 함수가있을 것입니다. 이것은 함수 포인터를 받아들이고 그 주소를 사용할 수있는 곳에 저장합니다. 그런 다음 지정된 이벤트가 트리거되면 해당 함수를 호출합니다.


0

예를 통해 아이디어를 이해하는 것이 훨씬 쉽습니다. 지금까지 C에서 콜백 함수에 대해 들었던 것은 훌륭한 답변이지만이 기능을 사용하면 얻을 수있는 가장 큰 이점은 코드를 깨끗하고 깔끔하게 유지하는 것입니다.

다음 C 코드는 빠른 정렬을 구현합니다. 아래 코드에서 가장 흥미로운 줄은 콜백 함수가 실제로 작동하는 것을 보여주는 것입니다.

qsort(arr,N,sizeof(int),compare_s2b);

compare_s2b는 qsort ()가 함수를 호출하는 데 사용하는 함수의 이름입니다. 이렇게하면 qsort ()가 깔끔하게 유지됩니다 (따라서 유지 관리가 더 쉽습니다). 다른 함수 내에서 이름으로 함수를 호출하면됩니다 (물론 함수 프로토 타입 선언은 다른 함수에서 호출하기 전에 미리 작성해야합니다).

완전한 코드

#include <stdio.h>
#include <stdlib.h>

int arr[]={56,90,45,1234,12,3,7,18};
//function prototype declaration 

int compare_s2b(const void *a,const void *b);

int compare_b2s(const void *a,const void *b);

//arranges the array number from the smallest to the biggest
int compare_s2b(const void* a, const void* b)
{
    const int* p=(const int*)a;
    const int* q=(const int*)b;

    return *p-*q;
}

//arranges the array number from the biggest to the smallest
int compare_b2s(const void* a, const void* b)
{
    const int* p=(const int*)a;
    const int* q=(const int*)b;

    return *q-*p;
}

int main()
{
    printf("Before sorting\n\n");

    int N=sizeof(arr)/sizeof(int);

    for(int i=0;i<N;i++)
    {
        printf("%d\t",arr[i]);
    }

    printf("\n");

    qsort(arr,N,sizeof(int),compare_s2b);

    printf("\nSorted small to big\n\n");

    for(int j=0;j<N;j++)
    {
        printf("%d\t",arr[j]);
    }

    qsort(arr,N,sizeof(int),compare_b2s);

    printf("\nSorted big to small\n\n");

    for(int j=0;j<N;j++)
    {
        printf("%d\t",arr[j]);
    }

    exit(0);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.