Bagaimana cara menangkap Ctrl+ Cdalam C?
Bagaimana cara menangkap Ctrl+ Cdalam C?
Jawaban:
Dengan pengendali sinyal.
Berikut adalah contoh sederhana membalik sebuah bool
digunakan di main()
:
#include <signal.h>
static volatile int keepRunning = 1;
void intHandler(int dummy) {
keepRunning = 0;
}
// ...
int main(void) {
signal(SIGINT, intHandler);
while (keepRunning) {
// ...
Sunting pada Juni 2017 : Kepada siapa pun yang berkepentingan, khususnya mereka yang memiliki keinginan yang tak pernah puas untuk mengedit jawaban ini. Lihat, saya menulis jawaban ini tujuh tahun yang lalu. Ya, standar bahasa berubah. Jika Anda benar-benar harus memperbaiki dunia, silakan tambahkan jawaban baru Anda tetapi tinggalkan jawaban saya apa adanya. Karena jawabannya ada nama saya di atasnya, saya lebih suka mengandung kata-kata saya juga. Terima kasih.
bool volatile keepRunning = true;
100% aman. Kompiler bebas untuk melakukan cache keepRunning
dalam register dan volatile akan mencegahnya. Dalam praktiknya, kemungkinan besar itu juga berfungsi tanpa kata kunci yang mudah menguap ketika loop sementara memanggil setidaknya satu fungsi non-inline.
sig_atomic_t
atau atomic_bool
mengetik di sana. Saya hanya merindukan yang itu. Sekarang, karena kita sedang berbicara: apakah Anda ingin saya mengembalikan suntingan terakhir saya? Tidak ada perasaan keras di sana, itu akan dapat dimengerti dari sudut pandang Anda :)
Periksa di sini:
Catatan: Jelas, ini adalah contoh sederhana menjelaskan hanya bagaimana mendirikan sebuah CtrlChandler, tapi seperti biasa ada aturan yang perlu dipatuhi agar tidak merusak sesuatu yang lain. Silakan baca komentar di bawah ini.
Kode sampel dari atas:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void INThandler(int);
int main(void)
{
signal(SIGINT, INThandler);
while (1)
pause();
return 0;
}
void INThandler(int sig)
{
char c;
signal(sig, SIG_IGN);
printf("OUCH, did you hit Ctrl-C?\n"
"Do you really want to quit? [y/n] ");
c = getchar();
if (c == 'y' || c == 'Y')
exit(0);
else
signal(SIGINT, INThandler);
getchar(); // Get new line character
}
int main
adalah hal yang tepat, tetapi gcc
dan kompiler lain mengkompilasi ini ke program yang berjalan dengan benar sejak tahun 1990-an. Dijelaskan dengan cukup baik di sini: eskimo.com/~scs/readings/voidmain.960823.html - ini pada dasarnya adalah "fitur", saya menganggapnya seperti itu.
Tambahan tentang platform UN * X.
Menurut signal(2)
halaman manual di GNU / Linux, perilaku signal
tidak portabel seperti perilaku sigaction
:
Perilaku sinyal () bervariasi antar versi UNIX, dan secara historis juga bervariasi di berbagai versi Linux. Hindari penggunaannya: gunakan sigaction (2) sebagai gantinya.
Pada Sistem V, sistem tidak menghalangi pengiriman instance sinyal lebih lanjut dan pengiriman sinyal akan mengatur ulang handler ke default. Di BSD semantik berubah.
Variasi berikut dari jawaban sebelumnya oleh Dirk Eddelbuettel menggunakan sigaction
alih-alih signal
:
#include <signal.h>
#include <stdlib.h>
static bool keepRunning = true;
void intHandler(int) {
keepRunning = false;
}
int main(int argc, char *argv[]) {
struct sigaction act;
act.sa_handler = intHandler;
sigaction(SIGINT, &act, NULL);
while (keepRunning) {
// main loop
}
}
Atau Anda dapat meletakkan terminal dalam mode mentah, seperti ini:
struct termios term;
term.c_iflag |= IGNBRK;
term.c_iflag &= ~(INLCR | ICRNL | IXON | IXOFF);
term.c_lflag &= ~(ICANON | ECHO | ECHOK | ECHOE | ECHONL | ISIG | IEXTEN);
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
tcsetattr(fileno(stdin), TCSANOW, &term);
Sekarang mungkin untuk membaca Ctrl+ Cmenggunakan penekanan tombol fgetc(stdin)
. Hati-hati menggunakan ini karena Anda tidak dapat Ctrl+ Z, Ctrl+ Q, Ctrl+ S, dll seperti biasanya lagi baik.
Siapkan jebakan (Anda dapat menjebak beberapa sinyal dengan satu pengendali):
sinyal (SIGQUIT, my_handler); sinyal (SIGINT, my_handler);
Tangani sinyalnya sesuai keinginan Anda, tetapi waspadai keterbatasan dan gotcha:
membatalkan my_handler (int sig) { / * Kode Anda di sini. * / }
@Peter Varo memperbarui jawaban Dirk, tetapi Dirk menolak perubahan itu. Inilah jawaban baru dari Peter:
Meskipun cuplikan di atas adalah benar c89Sebagai contoh, seseorang harus menggunakan tipe dan jaminan yang lebih modern yang disediakan oleh standar nanti jika memungkinkan. Oleh karena itu, berikut adalah alternatif yang lebih aman dan modern bagi mereka yang mencaric99 dan c11 implementasi sesuai:
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
static volatile sig_atomic_t keep_running = 1;
static void sig_handler(int _)
{
(void)_;
keep_running = 0;
}
int main(void)
{
signal(SIGINT, sig_handler);
while (keep_running)
puts("Still running...");
puts("Stopped by signal `SIGINT'");
return EXIT_SUCCESS;
}
C11 Standar: 7.14§2 Header
<signal.h>
menyatakan jenis ...sig_atomic_t
yang merupakan jenis integer (mungkin yang memenuhi syarat) dari objek yang dapat diakses sebagai entitas atom, bahkan di hadapan interupsi asinkron.
Selanjutnya:
Standar C11: 7.14.1.1§5 Jika sinyal muncul selain dari hasil pemanggilan fungsi
abort
atauraise
, perilaku tidak terdefinisi jika penangan sinyal mengacu pada objek apa pun denganstatic
atau durasi penyimpanan ulir yang bukan objek atom bebas kunci lainnya selain dengan menetapkan nilai ke objek yang dinyatakan sebagaivolatile sig_atomic_t
...
(void)_;
... Apa tujuannya? Apakah begitu kompiler tidak memperingatkan tentang variabel yang tidak digunakan?
Mengenai jawaban yang ada, perhatikan bahwa penanganan sinyal tergantung pada platform. Win32 misalnya menangani sinyal yang jauh lebih sedikit daripada sistem operasi POSIX; lihat di sini . Sementara SIGINT dideklarasikan dalam signal.h pada Win32, lihat catatan di dokumentasi yang menjelaskan bahwa itu tidak akan melakukan apa yang Anda harapkan.
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_handler(int signo)
{
if (signo == SIGINT)
printf("received SIGINT\n");
}
int main(void)
{
if (signal(SIGINT, sig_handler) == SIG_ERR)
printf("\ncan't catch SIGINT\n");
// A long long wait so that we can easily issue a signal to this process
while(1)
sleep(1);
return 0;
}
Fungsi sig_handler memeriksa apakah nilai argumen yang diteruskan sama dengan SIGINT, maka printf dieksekusi.
Ini hanya mencetak sebelum keluar.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void sigint_handler(int);
int main(void)
{
signal(SIGINT, sigint_handler);
while (1){
pause();
}
return 0;
}
void sigint_handler(int sig)
{
/*do something*/
printf("killing process %d\n",getpid());
exit(0);
}