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 int
dan a SignalHandler
dan mengembalikan a SignalHandler
.
Butuh sedikit membiasakan diri. Satu hal yang tidak dapat Anda lakukan, adalah menulis fungsi pengendali sinyal menggunakan SignalHandler
typedef
definisi 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 typedef
memperkenalkan 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 SIGALRM
seperti itu adalah satu-satunya sinyal yang menjadi handler, tetapi signal_handler()
mungkin mendapatkan SIGINT
atau SIGQUIT
sebagai 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
, SIGPIPE
dan SIGTERM
juga dan apakah mereka didefinisikan ( #ifdef
kompilasi 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_IGN
sebagai 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_IGN
dan signal()
mengembalikan penangan kesalahan sebelumnya, nilai old
setelah if
pernyataan 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_handler
adalah pointer ke fungsi dan oleh karena itu alarm_handler(1)
adalah doa fungsi melalui pointer fungsi.
Jadi, sejauh ini, saya telah menunjukkan bahwa SignalHandler
variabel 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 int
a dan double
sebagai dan mengembalikan double
nilai (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 SignalHandler
argumen 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 var
itu 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, typedef
diperlakukan sebagai kelas penyimpanan dalam tata bahasa, agak mirip static
dan extern
kelas 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 SignalHandler
argumen 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).