Tangkap Ctrl-C di C

158

Bagaimana cara menangkap Ctrl+ Cdalam C?

Feldor
sumber
5
Tidak ada hal seperti menangkap sinyal di C .... atau setidaknya jadi saya pikir sampai saya membaca standar C99. Ternyata ada penanganan sinyal yang didefinisikan dalam C tetapi Ctrl-C tidak diberi mandat untuk menghasilkan sinyal tertentu atau sinyal sama sekali. Tergantung pada platform Anda, ini mungkin tidak mungkin.
JeremyP
1
Penanganan sinyal sebagian besar tergantung pada implementasi. Pada platform * nix, gunakan <signal.h>, dan jika Anda menggunakan OSX, Anda dapat memanfaatkan GCD untuk membuat segalanya lebih mudah ~.
Dylan Lukes

Jawaban:

205

Dengan pengendali sinyal.

Berikut adalah contoh sederhana membalik sebuah booldigunakan 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.

Dirk Eddelbuettel
sumber
2
Katakan bahwa kita perlu menyertakan # signal.h> agar ini berfungsi!
kristianlm
13
statis Seharusnya bool volatile keepRunning = true;100% aman. Kompiler bebas untuk melakukan cache keepRunningdalam 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.
Johannes Overmann
2
@DirkEddelbuettel Saya berdiri dikoreksi, saya pikir perbaikan saya akan lebih mencerminkan niat asli Anda, maaf jika itu tidak terjadi. Pokoknya, masalah yang lebih besar adalah bahwa, karena jawaban Anda mencoba menjadi cukup umum, dan karena IMO itu harus memberikan potongan yang juga berfungsi di bawah interupsi asinkron: Saya akan menggunakan sig_atomic_tatau atomic_boolmengetik 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 :)
Peter Varo
2
Itu jauh lebih baik!
Dirk Eddelbuettel
2
@JohannesOvermann Bukannya saya ingin melakukan nitpick tetapi secara tegas, tidak masalah jika kode memanggil fungsi non-inline atau tidak, karena hanya ketika melintasi penghalang memori kompiler tidak diperbolehkan untuk mengandalkan nilai yang di-cache. Mengunci / Membuka kunci mutex akan menjadi penghalang memori. Karena variabel statis dan dengan demikian tidak terlihat di luar file saat ini, kompiler aman untuk menganggap bahwa suatu fungsi tidak pernah dapat mengubah nilainya, kecuali jika Anda meneruskan referensi ke variabel itu ke fungsi. Jadi volatile sangat disarankan di sini.
Mecki
47

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
}
icyrock.com
sumber
2
@ Derrick Agree, int mainadalah hal yang tepat, tetapi gccdan 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.
icyrock.com
1
@ icyrock.com: Semua sangat benar (tentang void main () di C), tetapi ketika memposting secara publik mungkin juga untuk menghindari perdebatan sama sekali dengan menggunakan int main () agar tidak mengalihkan perhatian dari titik utama.
Clifford
21
Ada kelemahan besar dalam hal ini. Anda tidak dapat menggunakan printf dengan aman dalam konten penangan sinyal. Ini merupakan pelanggaran keamanan sinyal asinkron. Ini karena printf tidak reentrant. Apa yang terjadi jika program berada di tengah-tengah penggunaan printf ketika Ctrl-C ditekan, dan pengendali sinyal Anda mulai menggunakannya pada saat yang sama? Petunjuk: Ini kemungkinan akan pecah. tulis dan fwrite sama untuk digunakan dalam konteks ini.
Dylan Lukes
2
@ icyrock.com: Melakukan sesuatu yang rumit dalam penangan sinyal akan menyebabkan sakit kepala. Terutama menggunakan sistem io.
Martin York
2
@ stacker Terima kasih - Saya pikir itu sangat berharga di akhir. Jika seseorang menemukan kode ini di masa depan, lebih baik untuk memperbaikinya sebanyak mungkin, terlepas dari topik pertanyaannya.
icyrock.com
30

Tambahan tentang platform UN * X.

Menurut signal(2)halaman manual di GNU / Linux, perilaku signaltidak 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 sigactionalih-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
    }
}
Filip J.
sumber
14

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.

Walter
sumber
11

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. * /
}
Paul Beckingham
sumber
8

@Peter Varo memperbarui jawaban Dirk, tetapi Dirk menolak perubahan itu. Inilah jawaban baru dari Peter:

Meskipun cuplikan di atas adalah benar Sebagai 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 mencari dan 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_tyang 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 abortatau raise, perilaku tidak terdefinisi jika penangan sinyal mengacu pada objek apa pun dengan staticatau durasi penyimpanan ulir yang bukan objek atom bebas kunci lainnya selain dengan menetapkan nilai ke objek yang dinyatakan sebagai volatile sig_atomic_t...

MCCCS
sumber
Saya tidak mengerti (void)_;... Apa tujuannya? Apakah begitu kompiler tidak memperingatkan tentang variabel yang tidak digunakan?
Luis Paulo
1
@LuisPaulo Ya untuk pertanyaan kedua.
MCCCS
Saya baru saja menemukan jawaban itu dan akan menempelkan tautan itu di sini! Terima kasih :)
Luis Paulo
5

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.

Clifford
sumber
3
#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.

Cinta bisaria
sumber
Jangan pernah panggil rutin I / O dalam penangan sinyal.
jwdonahue
2

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);
}
alemol
sumber
2
Jangan pernah memanggil I / O di penangan sinyal.
jwdonahue