C'de “geri arama” nedir ve nasıl uygulanır?


153

Yaptığım okumadan, Core Audio büyük ölçüde geri aramalara dayanıyor (ve C ++, ancak bu başka bir hikaye).

Bir görevi yerine getirmek için tekrar tekrar başka bir işlev tarafından çağrılan bir işlev kurma kavramını (türünü) anlıyorum. Nasıl kurulduklarını ve gerçekte nasıl çalıştıklarını anlamıyorum. Herhangi bir örnek takdir edilecektir.

Yanıtlar:


203

C'de "geri çağrı" yoktur - diğer genel programlama konseptlerinden daha fazla değildir.

Fonksiyon göstergeleri kullanılarak uygulanır. İşte bir örnek:

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);
    ...
}

Burada, populate_arrayişlev üçüncü parametre olarak bir işlev işaretçisi alır ve diziyi dolduracak değerleri almak için çağırır. getNextRandomValueRastgele bir ish değeri döndüren geri çağrıyı yazdık ve buna bir işaretçi ilettik populate_array. populate_arraygeri arama işlevimizi 10 kez arayacak ve döndürülen değerleri verilen dizideki öğelere atayacaktır.


2
Burada yanlış olabilir, ancak işlev işaretçisini çağıran populate_array satırında olmamalıdır: array [i] = (* getNextValue) (); ?
Nathan Fellman

40
Dereference operatörü, operatörün adresi gibi işlev işaretçileriyle isteğe bağlıdır. myfunc (...) = (* myfunc) (...) ve & myfunc = myfunc
aib

1
@NathanFellman Sadece okumak Uzman C Programlama ve iyi çağıran işlev işaretçisi açıklıyor.
Matt Clarkson

1
@johnny Çünkü standart böyle söylüyor. Yükseltilmiş yoruma bakın.
aib

3
@Patrick: populateArray bir kütüphanede (ve 12 yıl önce yazıldı) ve getNextRandomValue yazdınız (dün); bu yüzden doğrudan arayamaz. Karşılaştırıcıyı kendiniz sağladığınız bir kütüphane sıralama işlevini düşünün.
aib

121

İşte C'deki geri aramalara bir örnek.

Diyelim ki bir olay meydana geldiğinde geri çağrıların kaydedilmesini sağlayan bir kod yazmak istiyorsunuz.

Öncelikle geri arama için kullanılan işlev türünü tanımlayın:

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

Şimdi, bir geri çağrıyı kaydetmek için kullanılan bir işlevi tanımlayın:

int event_cb_register(event_cb_t cb, void *userdata);

Bir geri çağrıyı kaydeden kod şöyle görünecektir:

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);
...

Olay dağıtıcısının içinde, geri arama şuna benzer bir yapıda saklanabilir:

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

Kod bir geri arama yürüten gibi görünüyor.

struct event_cb *callback;

...

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

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

Tam ihtiyacım olan şey. Kullanıcılarınız geri arama işlevinde gerekli olan özel verileri (örn. Cihaz tutamaçları) iletmek istiyorsa kullanıcı verileri bölümü çok yararlıdır.
uceumern

doğrulama sorusu: İşlev adresine bir işaretçi olduğundan geri arama typedef yıldız işareti ile mi? Yıldız işareti eksikse, bu yanlış olur mu? Bu yanlışsa, github'daki cisco'nun libsrtp kütüphanesinde iki eksik yıldız vardır: github.com/cisco/libsrtp/blob/… github.com/cisco/libsrtp/blob/…
twildeman

@twildeman Standart C modunda uyarılarla derleyerek kendi sorunuza cevap vermek çok kolay görünüyor. Ayrıca, simge durumuna küçültülmüş bir test programı da yazabilirsiniz. Koddakiler gibi kod libsrtphiçbir uyarı vermez. Öyleyse, böyle bir tür bir işlev bağımsız değişkeni olarak göründüğünde, tıpkı dizilerin ilk öğelerine işaret edenlere çürümesi gibi, bir işaretçi-işleve 'çürümenin' gerekli olduğunu varsayıyorum, böylece aynı şey sonunda olur öyle ya da böyle. O ise buldum böyle typedefs tartışmalar daha ziyade, bu yönüyle de bile bakışta yapmak onunla prototipler veya işaretçileri ilan odaklanmak olduğunu, olsa da, ilginç
underscore_d

Bunun ne yaptığı hakkında hiçbir fikrim yok ve başarıyla derlenemiyor. Herkes başarılı bir şekilde açıklayabilir veya başarılı bir şekilde derlemek için kodun geri kalanını doldurabilir mi?
Andy Lin

20

Basit bir geri arama programı. Umarım sorunuzu cevaplar.

#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'deki bir geri çağırma işlevi, başka bir işlev içinde kullanılmak üzere atanan bir işlev parametresine / değişkenine eşdeğerdir. Wiki Örneği

Aşağıdaki kodda,

#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 işlev çağrısındaki (* numberSource) işlevi, PrintTwoNumbers içinden "geri çağırmak" / Çalıştırmak için kodun gerektirdiği şekilde yürütmek için kullanılan bir işlevdir.

Dolayısıyla, bir pthread işlevi gibi bir şeyiniz varsa, döngünün içinde örneklenmesinden başka bir işlev atayabilirsiniz.


6

C'deki geri arama, diğer işlev görevini yaparken bir noktada "geri aramak" için başka bir işleve sağlanan bir işlevdir.

Orada bir geri arama kullanılan iki yöntem senkron geri arama ve asenkron geri arama. Bazı görevleri yerine getirecek ve daha sonra görev tamamlandığında arayan kişiye geri dönecek olan başka bir işleve eşzamanlı geri çağrı sağlanır. Bir görevi başlatacak ve sonra muhtemelen tamamlanmamış görevle arayana geri dönecek olan başka bir işleve eşzamansız geri çağrı sağlanır.

Eşzamanlı geri arama genellikle, diğer işlevin görevin bir adımını devrettiği başka bir işleve bir temsilci sağlamak için kullanılır. Bu heyet Klasik örnekler fonksiyonları bsearch()ve qsort()C Standart Kütüphanesi'nden. Bu işlevlerin her ikisi de, işlevin sağladığı görev sırasında kullanılan bir geri çağrıyı alır, böylece aranan bsearch()veya tür olması durumunda aranan verilerin türünün qsort()işlev tarafından bilinmesine gerek kalmaz Kullanılmış.

Örneğin, burada bsearch()farklı karşılaştırma fonksiyonları, senkron geri çağrılar kullanan küçük bir örnek program bulunmaktadır . Veri karşılaştırmasını bir geri arama işlevine devredebilmemize izin vererek, bsearch()işlev çalışma zamanında ne tür bir karşılaştırma kullanmak istediğimize karar vermemizi sağlar. Bu bsearch()işlev zaman uyumludur çünkü işlev döndüğünde görev tamamlanmıştır.

#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);
    }
}

Zaman uyumsuz bir geri arama, geri arama sağladığımız çağrılan işlev döndüğünde, görevin tamamlanmayabileceğinden farklıdır. Bu geri arama türü genellikle, bir G / Ç işleminin başlatıldığı ve ardından tamamlandığında geri arama başlatıldığı zaman uyumsuz G / Ç ile kullanılır.

Aşağıdaki programda TCP bağlantı isteklerini dinlemek için bir soket oluşturuyoruz ve bir istek alındığında, dinlemeyi yapan işlev daha sonra sağlanan geri arama işlevini çağırıyor. Bu basit uygulama, telnetbaşka bir pencereye bağlanmaya çalışmak için yardımcı programı veya bir web tarayıcısını kullanırken bir pencerede çalıştırarak uygulanabilir.

WinSock kodunun çoğunu Microsoft'un https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx adresindekiaccept() işlevle sağladığı örnekten kaldırdım

Bu uygulama listen()bağlantı noktası 8282 kullanarak yerel ana bilgisayar, 127.0.0.1 üzerinde başlar , ya telnet 127.0.0.1 8282da kullanabilirsiniz http://127.0.0.1:8282/.

Bu örnek uygulama, Visual Studio 2017 Community Edition ile bir konsol uygulaması olarak oluşturuldu ve soketlerin Microsoft WinSock sürümünü kullanıyor. Bir Linux uygulaması için WinSock işlevlerinin Linux alternatifleriyle değiştirilmesi gerekir ve bunun yerine Windows iş parçacığı kitaplığı kullanılır 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);
}

Hem eşzamanlı hem de eşzamansız geri aramaları gösteren mükemmel yanıt. C- * NIX'te eşzamansız geri arama kullanımının bir başka somut örneği, eşzamansız sinyaller ve bunların sinyal işleyicileridir. Linux'ta [link] sinyal işleyicilerin nasıl işlendiğine dair mükemmel bir açıklama ( stackoverflow.com/questions/6949025/… ).
drlolly

4

C'deki geri çağrılar genellikle işlev işaretçileri ve ilişkili bir veri işaretçisi kullanılarak uygulanır. İşlevinizi on_event()ve veri işaretçilerinizi bir çerçeve işlevine iletirsiniz watch_events()(örneğin). Bir olay meydana geldiğinde, verileriniz ve bazı olaya özgü verilerle işleviniz çağrılır.

Geri aramalar da GUI programlamasında kullanılır. GTK + öğretici güzel bir bölüm vardır sinyalleri ve geri aramaları teorisi .


2

Bu wikipedia makalesinin C'de bir örneği var.

Bunun iyi bir örneği, Apache Web sunucusunu artırmak için yazılan yeni modüllerin ana apache sürecine işlev işaretçileri ileterek kaydolmalarıdır, böylece bu işlevler web sayfası isteklerini işlemek için geri çağrılır.


0

Genellikle bu, bir işlevin bellek konumunu gösteren özel bir değişken olan bir işlev işaretçisi kullanılarak yapılabilir. Ardından, işlevi belirli bağımsız değişkenlerle çağırmak için kullanabilirsiniz. Bu nedenle, muhtemelen geri arama işlevini ayarlayan bir işlev olacaktır. Bu, bir işlev işaretçisini kabul eder ve daha sonra bu adresi kullanılabileceği bir yerde saklar. Bundan sonra, belirtilen olay tetiklendiğinde, o işlevi çağırır.


0

Bir fikri örnek yoluyla anlamak çok daha kolaydır. Şimdiye kadar C'de geri arama işlevi hakkında söylenenler harika cevaplar, ancak muhtemelen bu özelliği kullanmanın en büyük yararı kodu temiz ve düzenli tutmaktır.

Misal

Aşağıdaki C kodu hızlı sıralama uygular. Aşağıdaki koddaki en ilginç satır, geri arama işlevini çalışırken görebildiğimiz satırdır:

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

Compare_s2b, işlevi çağırmak için qsort () işlevinin kullandığı işlevin adıdır. Bu, qsort () işlevini bu kadar düzenli tutar (dolayısıyla bakımı daha kolaydır). Bir işlevi başka bir işlevin içinden adıyla çağırmanız yeterlidir (tabii ki, en azından işlev prototipi bildirimi, başka bir işlevden çağrılmadan önce yazılmalıdır).

Kodun Tamamı

#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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.