Sejauh menyangkut standar C, jika Anda mentransmisikan penunjuk fungsi ke penunjuk fungsi dari tipe yang berbeda dan kemudian memanggilnya, itu adalah perilaku yang tidak ditentukan . Lihat Lampiran J.2 (informatif):
Perilaku tersebut tidak ditentukan dalam situasi berikut:
- Sebuah pointer digunakan untuk memanggil fungsi yang tipenya tidak kompatibel dengan tipe point-to (6.3.2.3).
Bagian 6.3.2.3, paragraf 8 berbunyi:
Sebuah penunjuk ke fungsi dari satu jenis dapat diubah menjadi penunjuk ke fungsi jenis lain dan kembali lagi; hasilnya akan dibandingkan dengan penunjuk asli. Jika penunjuk yang dikonversi digunakan untuk memanggil fungsi yang tipenya tidak kompatibel dengan tipe menunjuk-ke, perilakunya tidak ditentukan.
Jadi dengan kata lain, Anda dapat mentransmisikan penunjuk fungsi ke jenis penunjuk fungsi yang berbeda, melemparkannya kembali, dan memanggilnya, dan semuanya akan berfungsi.
Definisi kompatibel agak rumit. Ini dapat ditemukan di bagian 6.7.5.3, paragraf 15:
Untuk dua jenis fungsi agar kompatibel, keduanya harus menetapkan jenis kembalian yang kompatibel 127 .
Selain itu, daftar tipe parameter, jika keduanya ada, harus sesuai dalam jumlah parameter dan dalam penggunaan terminator elipsis; parameter yang sesuai harus memiliki tipe yang kompatibel. Jika satu tipe memiliki daftar tipe parameter dan tipe lainnya ditentukan oleh deklarator fungsi yang bukan merupakan bagian dari definisi fungsi dan berisi daftar pengenal kosong, daftar parameter tidak boleh memiliki terminator elipsis dan tipe dari setiap parameter harus kompatibel dengan jenis yang dihasilkan dari penerapan promosi argumen default. Jika satu tipe memiliki daftar tipe parameter dan tipe lainnya ditentukan oleh definisi fungsi yang berisi daftar pengenal (mungkin kosong), keduanya harus setuju dalam jumlah parameter, dan tipe dari setiap parameter prototipe harus kompatibel dengan tipe yang dihasilkan dari penerapan promosi argumen default ke tipe pengenal yang sesuai. (Dalam penentuan kompatibilitas tipe dan tipe komposit, setiap parameter yang dideklarasikan dengan fungsi atau tipe array dianggap memiliki tipe yang disesuaikan dan setiap parameter yang dideklarasikan dengan tipe yang memenuhi syarat dianggap memiliki versi yang tidak memenuhi syarat dari tipe yang dideklarasikan.)
127) Jika kedua jenis fungsi adalah '' gaya lama '', jenis parameter tidak dibandingkan.
Aturan untuk menentukan apakah dua jenis kompatibel dijelaskan di bagian 6.2.7, dan saya tidak akan mengutipnya di sini karena agak panjang, tetapi Anda dapat membacanya di draf standar C99 (PDF) .
Aturan yang relevan di sini ada di bagian 6.7.5.1, paragraf 2:
Untuk dua tipe penunjuk agar kompatibel, keduanya harus identik memenuhi syarat dan keduanya harus menjadi penunjuk ke jenis yang kompatibel.
Oleh karena itu, karena a void*
tidak kompatibel dengan a struct my_struct*
, penunjuk fungsi bertipe void (*)(void*)
tidak kompatibel dengan penunjuk fungsi jenis void (*)(struct my_struct*)
, jadi casting penunjuk fungsi ini secara teknis merupakan perilaku yang tidak terdefinisi.
Dalam praktiknya, Anda dapat dengan aman menggunakan pointer fungsi casting dalam beberapa kasus. Dalam konvensi pemanggilan x86, argumen didorong pada stack, dan semua pointer memiliki ukuran yang sama (4 byte dalam x86 atau 8 byte dalam x86_64). Memanggil pointer fungsi bermuara pada mendorong argumen pada stack dan melakukan lompatan tidak langsung ke target pointer fungsi, dan jelas tidak ada gagasan tentang tipe pada level kode mesin.
Hal-hal yang pasti tidak bisa Anda lakukan:
- Transmisikan di antara pointer fungsi dari konvensi panggilan yang berbeda. Anda akan mengacaukan tumpukan dan yang terbaik, crash, paling buruk, berhasil diam-diam dengan lubang keamanan besar yang menganga. Dalam pemrograman Windows, Anda sering melewatkan petunjuk fungsi. Win32 mengharapkan semua fungsi callback menggunakan
stdcall
konvensi pemanggilan (yang macro CALLBACK
, PASCAL
dan WINAPI
semua memperluas untuk). Jika Anda meneruskan penunjuk fungsi yang menggunakan konvensi panggilan C standar ( cdecl
), hal buruk akan terjadi.
- Dalam C ++, lakukan cast antara pointer fungsi anggota kelas dan pointer fungsi reguler. Ini sering membuat pemula C ++ tersandung. Fungsi anggota kelas memiliki
this
parameter tersembunyi , dan jika Anda mentransmisikan fungsi anggota ke fungsi biasa, tidak ada this
objek untuk digunakan, dan sekali lagi, banyak kejahatan akan terjadi.
Ide buruk lainnya yang terkadang berhasil tetapi juga merupakan perilaku yang tidak terdefinisi:
- Mentransmisikan antara penunjuk fungsi dan penunjuk biasa (mis. Mentransmisikan a
void (*)(void)
ke a void*
). Pointer fungsi belum tentu berukuran sama dengan pointer biasa, karena pada beberapa arsitektur mungkin terdapat informasi kontekstual tambahan. Ini mungkin akan bekerja dengan baik pada x86, tetapi ingatlah bahwa ini adalah perilaku yang tidak ditentukan.