Memahami typedef untuk pointer fungsi di C

237

Saya selalu sedikit bingung ketika saya membaca kode orang lain yang telah mengetik pointer untuk fungsi dengan argumen. Saya ingat bahwa saya perlu waktu beberapa saat untuk mendapatkan definisi seperti itu ketika mencoba memahami algoritma numerik yang ditulis dalam C beberapa waktu lalu. Jadi, bisakah Anda membagikan tips dan pemikiran Anda tentang cara menulis typedef yang bagus untuk fungsi pointer (Do's and Do's not), mengapa bermanfaat dan bagaimana memahami karya orang lain? Terima kasih!

nbro
sumber
1
Bisakah Anda memberikan beberapa contoh?
Artelius
2
Apakah maksud Anda bukan typedef untuk pointer fungsi, bukan makro untuk pointer fungsi? Saya telah melihat yang pertama tetapi tidak yang terakhir.
dave4420

Jawaban:

297

Pertimbangkan signal()fungsi dari standar C:

extern void (*signal(int, void(*)(int)))(int);

Jelas sangat jelas - ini adalah fungsi yang mengambil dua argumen, integer dan pointer ke fungsi yang mengambil integer sebagai argumen dan tidak menghasilkan apa-apa, dan itu ( signal()) mengembalikan pointer ke fungsi yang mengambil integer sebagai argumen dan mengembalikan tidak ada.

Jika Anda menulis:

typedef void (*SignalHandler)(int signum);

maka Anda dapat mendeklarasikan signal()sebagai:

extern  SignalHandler signal(int signum, SignalHandler handler);

Ini berarti hal yang sama, tetapi biasanya dianggap lebih mudah dibaca. Lebih jelas bahwa fungsi mengambil a intdan a SignalHandlerdan mengembalikan a SignalHandler.

Butuh sedikit membiasakan diri. Satu hal yang tidak dapat Anda lakukan, adalah menulis fungsi pengendali sinyal menggunakan SignalHandler typedefdefinisi fungsi.

Saya masih dari old-school yang lebih suka memanggil fungsi pointer sebagai:

(*functionpointer)(arg1, arg2, ...);

Sintaks modern hanya menggunakan:

functionpointer(arg1, arg2, ...);

Saya bisa melihat mengapa itu bekerja - saya hanya lebih suka tahu bahwa saya perlu mencari di mana variabel diinisialisasi daripada untuk fungsi yang disebut functionpointer.


Sam berkomentar:

Saya telah melihat penjelasan ini sebelumnya. Dan kemudian, seperti halnya sekarang, saya pikir apa yang tidak saya dapatkan adalah hubungan antara dua pernyataan:

    extern void (*signal(int, void()(int)))(int);  /*and*/

    typedef void (*SignalHandler)(int signum);
    extern SignalHandler signal(int signum, SignalHandler handler);

Atau, yang ingin saya tanyakan adalah, apa konsep dasar yang dapat digunakan seseorang untuk menghasilkan versi kedua yang Anda miliki? Apa dasar yang menghubungkan "SignalHandler" dan typedef pertama? Saya pikir apa yang perlu dijelaskan di sini adalah apa yang sebenarnya dilakukan typedef di sini.

Mari kita coba lagi. Yang pertama diangkat langsung dari standar C - saya mengetik ulang, dan memeriksa bahwa saya memiliki tanda kurung yang benar (tidak sampai saya memperbaikinya - ini adalah cookie yang sulit untuk diingat).

Pertama-tama, ingat bahwa typedefmemperkenalkan alias untuk suatu tipe. Jadi, alias adalah SignalHandler, dan tipenya adalah:

pointer ke fungsi yang mengambil integer sebagai argumen dan tidak mengembalikan apa pun.

Bagian 'mengembalikan apa-apa' dieja void; argumen yang merupakan bilangan bulat adalah (saya percaya) cukup jelas. Notasi berikut hanya (atau tidak) bagaimana C spell pointer berfungsi mengambil argumen seperti yang ditentukan dan mengembalikan tipe yang diberikan:

type (*function)(argtypes);

Setelah membuat jenis penangan sinyal, saya bisa menggunakannya untuk mendeklarasikan variabel dan sebagainya. Sebagai contoh:

static void alarm_catcher(int signum)
{
    fprintf(stderr, "%s() called (%d)\n", __func__, signum);
}

static void signal_catcher(int signum)
{
    fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum);
    exit(1);
}

static struct Handlers
{
    int              signum;
    SignalHandler    handler;
} handler[] =
{
    { SIGALRM,   alarm_catcher  },
    { SIGINT,    signal_catcher },
    { SIGQUIT,   signal_catcher },
};

int main(void)
{
    size_t num_handlers = sizeof(handler) / sizeof(handler[0]);
    size_t i;

    for (i = 0; i < num_handlers; i++)
    {
        SignalHandler old_handler = signal(handler[i].signum, SIG_IGN);
        if (old_handler != SIG_IGN)
            old_handler = signal(handler[i].signum, handler[i].handler);
        assert(old_handler == SIG_IGN);
    }

    ...continue with ordinary processing...

    return(EXIT_SUCCESS);
}

Perhatian Bagaimana cara menghindari menggunakan printf()pengatur sinyal?

Jadi, apa yang telah kita lakukan di sini - selain menghilangkan 4 header standar yang diperlukan untuk membuat kode dikompilasi dengan bersih?

Dua fungsi pertama adalah fungsi yang mengambil integer tunggal dan tidak mengembalikan apa pun. Salah satu dari mereka sebenarnya tidak kembali sama sekali, exit(1);tetapi yang lain kembali setelah mencetak pesan. Ketahuilah bahwa standar C tidak mengizinkan Anda melakukan banyak hal di dalam penangan sinyal; POSIX sedikit lebih murah hati dalam apa yang diizinkan, tetapi secara resmi tidak memberi sanksi pada panggilan fprintf(). Saya juga mencetak nomor sinyal yang diterima. Dalam alarm_handler()fungsi tersebut, nilainya akan selalu SIGALRMseperti itu adalah satu-satunya sinyal yang menjadi handler, tetapi signal_handler()mungkin mendapatkan SIGINTatau SIGQUITsebagai nomor sinyal karena fungsi yang sama digunakan untuk keduanya.

Lalu saya membuat array struktur, di mana setiap elemen mengidentifikasi nomor sinyal dan pawang yang dipasang untuk sinyal itu. Saya memilih untuk mengkhawatirkan 3 sinyal; Saya sering khawatir SIGHUP, SIGPIPEdan SIGTERMjuga dan apakah mereka didefinisikan ( #ifdefkompilasi bersyarat), tetapi itu hanya mempersulit. Saya mungkin juga menggunakan POSIX sigaction()sebagai ganti signal(), tapi itu masalah lain; mari kita tetap dengan apa yang kita mulai.

The main()iterates fungsi di atas daftar penangan yang akan diinstal. Untuk setiap penangan, pertama-tama panggilan signal()untuk mencari tahu apakah proses saat ini mengabaikan sinyal, dan saat melakukannya, dipasang SIG_IGNsebagai penangan, yang memastikan bahwa sinyal tetap diabaikan. Jika sinyal sebelumnya tidak diabaikan, maka ia akan memanggil signal()lagi, kali ini untuk memasang penangan sinyal yang disukai. (Nilai lainnya mungkin SIG_DFL, penangan sinyal default untuk sinyal.) Karena panggilan pertama ke 'sinyal ()' mengatur penangan ke SIG_IGNdan signal()mengembalikan penangan kesalahan sebelumnya, nilai oldsetelah ifpernyataan harus SIG_IGN- maka pernyataan. (Yah, bisa jadi ituSIG_ERR jika ada yang salah secara dramatis - tapi kemudian saya akan belajar tentang hal itu dari penembakan tegas.)

Program kemudian melakukan tugasnya dan keluar secara normal.

Perhatikan bahwa nama fungsi dapat dianggap sebagai penunjuk ke fungsi dengan tipe yang sesuai. Ketika Anda tidak menerapkan kurung fungsi-panggilan - seperti dalam inisialisasi, misalnya - nama fungsi menjadi penunjuk fungsi. Ini juga mengapa masuk akal untuk menjalankan fungsi melalui pointertofunction(arg1, arg2)notasi; Ketika Anda melihat alarm_handler(1), Anda dapat mempertimbangkan bahwa itu alarm_handleradalah pointer ke fungsi dan oleh karena itu alarm_handler(1)adalah doa fungsi melalui pointer fungsi.

Jadi, sejauh ini, saya telah menunjukkan bahwa SignalHandlervariabel relatif lurus ke depan untuk digunakan, selama Anda memiliki beberapa jenis nilai yang tepat untuk ditugaskan padanya - yang merupakan apa yang disediakan oleh dua fungsi pengendali sinyal.

Sekarang kita kembali ke pertanyaan - bagaimana kedua deklarasi untuk signal()saling berhubungan.

Mari kita tinjau deklarasi kedua:

 extern SignalHandler signal(int signum, SignalHandler handler);

Jika kami mengubah nama fungsi dan jenisnya seperti ini:

 extern double function(int num1, double num2);

Anda tidak akan memiliki masalah menafsirkan ini sebagai fungsi yang mengambil argumen inta dan doublesebagai dan mengembalikan doublenilai (kan? Anda mungkin lebih baik tidak mengaku jika itu bermasalah - tapi mungkin Anda harus berhati-hati dalam mengajukan pertanyaan dengan keras seperti ini jika ini masalah).

Sekarang, alih-alih menjadi double, signal()fungsi mengambil SignalHandlerargumen kedua, dan mengembalikannya sebagai hasilnya.

Mekanika yang dengannya juga dapat diperlakukan sebagai:

extern void (*signal(int signum, void(*handler)(int signum)))(int signum);

sulit untuk dijelaskan - jadi saya mungkin akan mengacaukannya. Kali ini saya telah memberikan nama parameter - meskipun nama tidak kritis.

Secara umum, dalam C, mekanisme deklarasi adalah sedemikian rupa sehingga jika Anda menulis:

type var;

maka ketika Anda menulis varitu mewakili nilai yang diberikan type. Sebagai contoh:

int     i;            // i is an int
int    *ip;           // *ip is an int, so ip is a pointer to an integer
int     abs(int val); // abs(-1) is an int, so abs is a (pointer to a)
                      // function returning an int and taking an int argument

Dalam standar, typedefdiperlakukan sebagai kelas penyimpanan dalam tata bahasa, agak mirip staticdan externkelas penyimpanan.

typedef void (*SignalHandler)(int signum);

berarti bahwa ketika Anda melihat variabel tipe SignalHandler(misalkan alarm_handler) dipanggil sebagai:

(*alarm_handler)(-1);

hasilnya telah type void- tidak ada hasil. Dan (*alarm_handler)(-1);merupakan doa alarm_handler()dengan argumen -1.

Jadi, jika kita menyatakan:

extern SignalHandler alt_signal(void);

itu berarti:

(*alt_signal)();

mewakili nilai void. Dan oleh karena itu:

extern void (*alt_signal(void))(int signum);

setara. Sekarang, signal()lebih kompleks karena tidak hanya mengembalikan argumen SignalHandler, tetapi juga menerima SignalHandlerargumen int dan a sebagai:

extern void (*signal(int signum, SignalHandler handler))(int signum);

extern void (*signal(int signum, void (*handler)(int signum)))(int signum);

Jika itu masih membingungkan Anda, saya tidak yakin bagaimana cara membantu - itu masih pada beberapa tingkat misterius bagi saya, tetapi saya sudah terbiasa dengan cara kerjanya dan karena itu dapat memberi tahu Anda bahwa jika Anda tetap menggunakannya selama 25 tahun lagi atau lebih, itu akan menjadi kebiasaan Anda (dan mungkin bahkan sedikit lebih cepat jika Anda pintar).

Jonathan Leffler
sumber
3
Saya telah melihat penjelasan ini sebelumnya. Dan kemudian, seperti halnya sekarang, saya pikir apa yang saya tidak dapatkan adalah hubungan antara dua pernyataan: extern void ( sinyal (int, void ( ) (int))) (int); / * dan * / typedef batal (* SignalHandler) (int signum); extern SignalHandler signal (int signum, SignalHandler handler); Atau, yang ingin saya tanyakan adalah, apa konsep dasar yang dapat digunakan seseorang untuk menghasilkan versi kedua yang Anda miliki? Apa dasar yang menghubungkan "SignalHandler" dan typedef pertama? Saya pikir apa yang perlu dijelaskan di sini adalah apa yang sebenarnya dilakukan typedef di sini. Thx
6
Jawaban yang bagus, saya senang saya kembali ke utas ini. Saya pikir saya tidak mengerti segalanya, tetapi suatu hari saya akan mengerti. Ini sebabnya saya suka SO. Terima kasih.
toto
2
Hanya untuk memilih nit: tidak aman untuk memanggil printf () dan teman-teman di dalam pengatur sinyal; printf () tidak masuk kembali (pada dasarnya karena itu dapat memanggil malloc (), yang bukan reentrant)
wildplasser
4
The extern void (*signal(int, void(*)(int)))(int);berarti satu signal(int, void(*)(int))fungsi akan mengembalikan fungsi pointer ke void f(int). Saat Anda ingin menentukan pointer fungsi sebagai nilai balik , sintaksnya menjadi rumit. Anda harus menempatkan tipe nilai pengembalian ke kiri dan daftar argumen di sebelah kanan , sementara itu adalah tengah yang Anda tetapkan. Dan dalam hal ini, signal()fungsi itu sendiri mengambil penunjuk fungsi sebagai parameternya, yang memperumit banyak hal. Berita baiknya adalah, jika Anda dapat membaca yang ini, the Force sudah bersama Anda. :)
smwikipedia
1
Apa sekolah tua tentang menggunakan &di depan nama fungsi? Ini sama sekali tidak perlu; sia-sia, bahkan. Dan jelas bukan "sekolah tua". Sekolah lama menggunakan nama fungsi yang sederhana dan sederhana.
Jonathan Leffler
80

Pointer fungsi seperti pointer lainnya, tetapi menunjuk ke alamat fungsi alih-alih alamat data (pada heap atau stack). Seperti halnya pointer apa pun, itu perlu diketik dengan benar. Fungsi ditentukan oleh nilai baliknya dan jenis parameter yang diterimanya. Jadi untuk sepenuhnya menggambarkan suatu fungsi, Anda harus memasukkan nilai baliknya dan jenis dari setiap parameter diterima. Ketika Anda mengetik definisi seperti itu, Anda memberinya 'nama yang bersahabat' yang membuatnya lebih mudah untuk membuat dan referensi referensi menggunakan definisi itu.

Jadi misalnya anggap Anda memiliki fungsi:

float doMultiplication (float num1, float num2 ) {
    return num1 * num2; }

maka typedef berikut:

typedef float(*pt2Func)(float, float);

dapat digunakan untuk menunjuk ke doMulitplicationfungsi ini . Ini hanya mendefinisikan pointer ke fungsi yang mengembalikan float dan mengambil dua parameter, masing-masing tipe float. Definisi ini memiliki nama yang ramah pt2Func. Catatan yang pt2Funcdapat menunjuk ke fungsi APA SAJA yang mengembalikan float dan mengambil 2 float.

Jadi Anda bisa membuat pointer yang menunjuk ke fungsi doMultiplication sebagai berikut:

pt2Func *myFnPtr = &doMultiplication;

dan Anda dapat menjalankan fungsi menggunakan pointer ini sebagai berikut:

float result = (*myFnPtr)(2.0, 5.1);

Ini bacaan yang bagus: http://www.newty.de/fpt/index.html

psikotik
sumber
psikotik, terima kasih! Itu sangat membantu. Tautan ke halaman web fungsi pointer sangat membantu. Bacalah itu sekarang.
... Namun, tautan newty.de itu tampaknya tidak membicarakan typedefs sama sekali :( Jadi meskipun tautan itu hebat, tetapi respons di utas ini tentang typedefs sangat berharga!
11
Anda mungkin ingin melakukan pt2Func myFnPtr = &doMultiplication;alih - alih pt2Func *myFnPtr = &doMultiplication;seperti myFnPtryang sudah menjadi pointer.
Tamilselvan
1
mendeklarasikan pt2Func * myFnPtr = & doMultiplication; bukannya pt2Func myFnPtr = & doMultiplication; melempar peringatan.
AlphaGoku
2
@ Tamilselvan benar. myFunPtrsudah menjadi penunjuk fungsi jadi gunakanpt2Func myFnPtr = &doMultiplication;
Dustin Biser
35

Cara yang sangat mudah untuk memahami typedef dari pointer fungsi:

int add(int a, int b)
{
    return (a+b);
}

typedef int (*add_integer)(int, int); //declaration of function pointer

int main()
{
    add_integer addition = add; //typedef assigns a new variable i.e. "addition" to original function "add"
    int c = addition(11, 11);   //calling function via new variable
    printf("%d",c);
    return 0;
}
pengguna2786027
sumber
32

cdecladalah alat yang hebat untuk menguraikan sintaks aneh seperti deklarasi fungsi pointer. Anda dapat menggunakannya untuk menghasilkannya juga.

Sejauh tips untuk membuat deklarasi rumit lebih mudah diurai untuk pemeliharaan di masa depan (sendiri atau orang lain), saya sarankan membuat typedefpotongan kecil dan menggunakan potongan-potongan kecil sebagai blok bangunan untuk ekspresi yang lebih besar dan lebih rumit. Sebagai contoh:

typedef int (*FUNC_TYPE_1)(void);
typedef double (*FUNC_TYPE_2)(void);
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2);

daripada:

typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void);

cdecl dapat membantu Anda dengan hal-hal ini:

cdecl> explain int (*FUNC_TYPE_1)(void)
declare FUNC_TYPE_1 as pointer to function (void) returning int
cdecl> explain double (*FUNC_TYPE_2)(void)
declare FUNC_TYPE_2 as pointer to function (void) returning double
cdecl> declare FUNC_TYPE_3 as pointer to function (pointer to function (void) returning double) returning pointer to function (void) returning int
int (*(*FUNC_TYPE_3)(double (*)(void )))(void )

Dan (sebenarnya) adalah persis bagaimana saya menghasilkan kekacauan gila di atas.

Carl Norum
sumber
2
Hai Carl, itu adalah contoh dan penjelasan yang sangat mendalam. Juga, terima kasih telah menunjukkan penggunaan cdecl. Sangat dihargai.
Apakah ada cdecl untuk windows?
Jack
@ Jack, saya yakin Anda bisa membangunnya, ya.
Carl Norum
2
Ada juga cdecl.org yang menyediakan jenis kemampuan yang sama tetapi online. Berguna untuk kita para pengembang Windows.
zaknotzach
12
int add(int a, int b)
{
  return (a+b);
}
int minus(int a, int b)
{
  return (a-b);
}

typedef int (*math_func)(int, int); //declaration of function pointer

int main()
{
  math_func addition = add;  //typedef assigns a new variable i.e. "addition" to original function "add"
  math_func substract = minus; //typedef assigns a new variable i.e. "substract" to original function "minus"

  int c = addition(11, 11);   //calling function via new variable
  printf("%d\n",c);
  c = substract(11, 5);   //calling function via new variable
  printf("%d",c);
  return 0;
}

Output dari ini adalah:

22

6

Perhatikan bahwa, definer math_func yang sama telah digunakan untuk mendeklarasikan kedua fungsi tersebut.

Pendekatan typedef yang sama dapat digunakan untuk extern struct. (Menggunakan sturuct di file lain.)

Harshal Doshi Jain
sumber
5

Gunakan typedef untuk mendefinisikan tipe yang lebih rumit, yaitu pointer fungsi

Saya akan mengambil contoh mendefinisikan mesin-negara di C

    typedef  int (*action_handler_t)(void *ctx, void *data);

sekarang kita telah mendefinisikan tipe yang disebut action_handler yang mengambil dua pointer dan mengembalikan sebuah int

tentukan state-machine Anda

    typedef struct
    {
      state_t curr_state;   /* Enum for the Current state */
      event_t event;  /* Enum for the event */
      state_t next_state;   /* Enum for the next state */
      action_handler_t event_handler; /* Function-pointer to the action */

     }state_element;

Penunjuk fungsi untuk tindakan terlihat seperti tipe sederhana dan typedef terutama melayani tujuan ini.

Semua penangan acara saya sekarang harus mematuhi jenis yang ditentukan oleh action_handler

    int handle_event_a(void *fsm_ctx, void *in_msg );

    int handle_event_b(void *fsm_ctx, void *in_msg );

Referensi:

Pemrograman C ahli oleh Linden

vaaz
sumber
4

Ini adalah contoh paling sederhana dari pointer fungsi dan array fungsi pointer yang saya tulis sebagai latihan.

    typedef double (*pf)(double x);  /*this defines a type pf */

    double f1(double x) { return(x+x);}
    double f2(double x) { return(x*x);}

    pf pa[] = {f1, f2};


    main()
    {
        pf p;

        p = pa[0];
        printf("%f\n", p(3.0));
        p = pa[1];
        printf("%f\n", p(3.0));
    }
Bing Bang
sumber