“ การติดต่อกลับ” ใน 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ซึ่งจะส่งกลับค่าสุ่ม ish populate_arrayและผ่านตัวชี้ไปเพื่อ populate_arrayจะเรียกฟังก์ชันการเรียกกลับของเรา 10 ครั้งและกำหนดค่าที่ส่งคืนให้กับองค์ประกอบในอาร์เรย์ที่กำหนด


2
ฉันอาจจะผิดที่นี่ แต่ไม่ควรบรรทัดใน populate_array ที่เรียกตัวชี้ฟังก์ชั่น: array [i] = (* getNextValue) (); ?
Nathan Fellman

40
โอเปอเรเตอร์ dereference เป็นทางเลือกที่มีพอยน์เตอร์ของฟังก์ชั่นเช่นเดียวกับแอดเดรสของโอเปอเรเตอร์ myfunc (... ) = (* myfunc) (... ) และ & myfunc = myfunc
aib

1
@NathanFellman ฉันเพิ่งอ่านการเขียนโปรแกรมผู้เชี่ยวชาญ Cและอธิบายการทำงานของตัวชี้การโทรได้ดี
Matt Clarkson

1
@ Johnny เพราะมาตรฐานพูดอย่างนั้น ดูความคิดเห็น upvoted
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);
...

ใน internals ของตัวแจกจ่ายเหตุการณ์การเรียกกลับอาจถูกเก็บไว้ใน struct ที่มีลักษณะดังนี้:

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

คำถามการตรวจสอบ: callback typedef มีเครื่องหมายดอกจันเพราะเป็นตัวชี้ไปยังที่อยู่ฟังก์ชันหรือไม่ หากเครื่องหมายดอกจันหายไปจะไม่ถูกต้องหรือไม่ หากไม่ถูกต้องแสดงว่ามีดาวที่หายไปสองแห่งในไลบรารี libsrtp ของ cisco ใน github: github.com/cisco/libsrtp/blob/… github.com/cisco/libsrtp/blob/ …
twildeman

@tildildeman ดูเหมือนจะไม่สำคัญที่จะตอบคำถามของคุณเองโดยการรวบรวมในโหมด C มาตรฐานพร้อมคำเตือน คุณยังสามารถเขียนโปรแกรมทดสอบที่ย่อเล็กสุดได้ รหัสเช่นที่อยู่ในนั้นlibsrtpไม่มีคำเตือน ฉันเข้าใจว่าเมื่อชนิดดังกล่าวปรากฏเป็นอาร์กิวเมนต์ของฟังก์ชันจำเป็นต้อง 'decay' ไปยังตัวชี้ไปยังฟังก์ชันเช่นเดียวกับอาร์เรย์ที่สลายตัวเพื่อชี้ไปยังองค์ประกอบแรกของพวกเขาดังนั้นสิ่งเดียวกันจึงเกิดขึ้นในที่สุด ทางใดทางหนึ่ง อย่างไรก็ตามเป็นเรื่องที่น่าสนใจว่าการอภิปรายเกี่ยวกับประเภทของสิ่งที่ฉันพบไม่ได้มองในแง่มุมนี้เลย แต่ให้เน้นไปที่การประกาศต้นแบบหรือพอยน์เตอร์ด้วย
underscore_d

ฉันไม่รู้ว่ามันทำอะไรและไม่สามารถรวบรวมได้สำเร็จ ทุกคนสามารถอธิบายได้อย่างละเอียดหรือเติมรหัสที่เหลือเพื่อรวบรวมได้สำเร็จหรือไม่
Andy Lin

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

ฟังก์ชั่น (* numberSource) ภายในฟังก์ชั่นการโทร PrintTwoNumbers เป็นฟังก์ชั่น "โทรกลับ" / ดำเนินการจากภายใน PrintTwoNumbers ตามที่รหัสกำหนดในขณะที่มันทำงาน

ดังนั้นถ้าคุณมีบางอย่างเช่นฟังก์ชั่น pthread คุณสามารถกำหนดฟังก์ชั่นอื่นให้ทำงานในลูปจากอินสแตนซ์ของมัน


6

การเรียกกลับใน C คือฟังก์ชั่นที่มีให้กับฟังก์ชั่นอื่นเพื่อ "โทรกลับไปที่" ในบางจุดเมื่อฟังก์ชั่นอื่น ๆ ที่จะทำงานของมัน

มีสองวิธีที่ใช้โทรกลับ : โทรกลับซิงโครนัสและโทรกลับไม่ตรงกัน การโทรกลับแบบซิงโครนัสมีให้ในฟังก์ชันอื่นซึ่งกำลังจะทำงานบางอย่างจากนั้นกลับไปที่ผู้โทรโดยงานจะเสร็จสมบูรณ์ การโทรกลับแบบอะซิงโครนัสมีให้ในฟังก์ชันอื่นซึ่งกำลังจะเริ่มงานจากนั้นกลับไปที่ผู้โทรด้วยงานที่อาจไม่เสร็จสมบูรณ์

โดยปกติแล้วการโทรกลับแบบซิงโครนัสจะใช้เพื่อมอบตัวแทนให้กับฟังก์ชันอื่นซึ่งฟังก์ชันอื่นมอบหมายหน้าที่บางขั้นตอนให้กับงาน ตัวอย่างคลาสสิกของการมอบหมายนี้คือฟังก์ชั่นbsearch()และqsort()จาก C Standard Library ทั้งสองฟังก์ชั่นเหล่านี้ใช้การเรียกกลับที่ใช้ในระหว่างงานฟังก์ชั่นให้เพื่อให้ชนิดของข้อมูลที่ถูกค้นหาในกรณีของ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ยูทิลิตี้หรือเว็บเบราว์เซอร์เพื่อพยายามเชื่อมต่อในหน้าต่างอื่น

ฉันยกโค้ด WinSock ส่วนใหญ่จากตัวอย่างที่ Microsoft ให้ไว้พร้อมกับaccept()ฟังก์ชั่นที่https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx

โปรแกรมนี้จะเริ่มต้นlisten()ในพื้นที่ท้องถิ่น 127.0.0.1 โดยใช้พอร์ต 8282 เพื่อให้คุณสามารถใช้อย่างใดอย่างหนึ่งหรือtelnet 127.0.0.1 8282http://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 [link] ( stackoverflow.com/questions/6949025/… )
drlolly

4

การโทรกลับใน C มักจะนำมาใช้โดยใช้ตัวชี้ฟังก์ชั่นและตัวชี้ข้อมูลที่เกี่ยวข้อง คุณส่งต่อฟังก์ชันon_event()และตัวชี้ข้อมูลไปยังฟังก์ชันกรอบงานwatch_events()(ตัวอย่าง) เมื่อมีเหตุการณ์เกิดขึ้นฟังก์ชันของคุณจะถูกเรียกพร้อมกับข้อมูลของคุณและข้อมูลเฉพาะบางเหตุการณ์

การโทรกลับยังใช้ในการเขียนโปรแกรม GUI ดี + กวดวิชามีส่วนดีในทฤษฎีของสัญญาณและเรียกกลับ


2

นี้บทความวิกิพีเดียมีตัวอย่างในเซลเซียส

ตัวอย่างที่ดีคือโมดูลใหม่ที่เขียนขึ้นเพื่อเพิ่ม Apache เว็บเซิร์ฟเวอร์ลงทะเบียนด้วยกระบวนการ apache หลักโดยส่งผ่านพอยน์เตอร์ฟังก์ชั่นเพื่อให้ฟังก์ชั่นเหล่านั้นถูกเรียกกลับไปยังการประมวลผลคำขอหน้าเว็บ


0

โดยปกติสิ่งนี้สามารถทำได้โดยใช้ตัวชี้ฟังก์ชันนั่นคือตัวแปรพิเศษที่ชี้ไปยังตำแหน่งหน่วยความจำของฟังก์ชัน จากนั้นคุณสามารถใช้ฟังก์ชันนี้เพื่อเรียกใช้ฟังก์ชันที่มีอาร์กิวเมนต์ที่ระบุ ดังนั้นอาจจะมีฟังก์ชั่นที่ตั้งค่าฟังก์ชั่นการโทรกลับ สิ่งนี้จะยอมรับตัวชี้ฟังก์ชันแล้วเก็บที่อยู่นั้นไว้ที่ไหนสักแห่งที่สามารถใช้ได้ หลังจากนั้นเมื่อมีการเรียกใช้เหตุการณ์ที่ระบุจะเรียกใช้ฟังก์ชันนั้น


0

มันง่ายกว่ามากที่จะเข้าใจแนวคิดผ่านตัวอย่าง สิ่งที่ได้รับการบอกเล่าเกี่ยวกับฟังก์ชั่นการโทรกลับใน C จนถึงขณะนี้เป็นคำตอบที่ดี แต่อาจเป็นประโยชน์ที่ใหญ่ที่สุดของการใช้คุณลักษณะนี้คือการรักษารหัสให้สะอาดและไม่กระจาย

ตัวอย่าง

รหัส C ต่อไปนี้ใช้การเรียงลำดับอย่างรวดเร็ว บรรทัดที่น่าสนใจที่สุดในโค้ดด้านล่างนี้คือบรรทัดนี้ซึ่งเราสามารถเห็นฟังก์ชันการเรียกกลับ:

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

compar_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.