Cattura Ctrl-C in C


158

Come si fa a catturare Ctrl+ Cin C?


5
Non c'è niente come catturare segnali in C .... o almeno così ho pensato fino a quando ho letto lo standard C99. Si scopre che esiste una gestione del segnale definita in C ma Ctrl-C non è obbligato a produrre alcun segnale specifico o segnale. A seconda della piattaforma, questo potrebbe essere impossibile.
JeremyP,

1
La gestione del segnale dipende principalmente dall'implementazione. Su piattaforme * nix, usa <signal.h>, e se sei su OSX puoi sfruttare GCD per rendere le cose ancora più facili ~.
Dylan Lukes,

Risposte:


205

Con un gestore di segnale.

Ecco un semplice esempio lanciando un boolusato in main():

#include <signal.h>

static volatile int keepRunning = 1;

void intHandler(int dummy) {
    keepRunning = 0;
}

// ...

int main(void) {

   signal(SIGINT, intHandler);

   while (keepRunning) { 
      // ...

Modifica a giugno 2017 : a chi potrebbe interessare, in particolare quelli con un insaziabile bisogno di modificare questa risposta. Senti, ho scritto questa risposta sette anni fa. Sì, gli standard linguistici cambiano. Se devi davvero migliorare il mondo, aggiungi la tua nuova risposta ma lascia la mia così com'è. Poiché la risposta ha il mio nome, preferirei che contenga anche le mie parole. Grazie.


2
Diciamo che dobbiamo #include <signal.h> perché questo funzioni!
kristianlm,

13
statico Dovrebbe essere bool volatile keepRunning = true;sicuro al 100%. Il compilatore è libero di memorizzare keepRunningnella cache in un registro e la volatile lo impedirà. In pratica, molto probabilmente può funzionare anche senza la parola chiave volatile quando il ciclo while chiama almeno una funzione non in linea.
Johannes Overmann,

2
@DirkEddelbuettel Sono corretto, pensavo che i miei miglioramenti riflettessero maggiormente le tue intenzioni originali, scusami se non è successo. Comunque, il problema maggiore è che, poiché la tua risposta cerca di essere abbastanza generica, e poiché IMO dovrebbe fornire uno snippet che funziona anche con interruzioni asincrone: utilizzerei sig_atomic_to atomic_booldigiterei lì. Mi è mancato proprio quello. Ora, dal momento che stiamo parlando: vuoi che esegua il rollback della mia ultima modifica? Nessun sentimento duro lì sarebbe perfettamente comprensibile dal tuo punto di vista :)
Peter Varo,

2
È molto meglio!
Dirk Eddelbuettel,

2
@JohannesOvermann Non che io voglia fare il nitpick ma a rigor di termini, non importa se il codice chiama o meno funzioni non inline, poiché solo quando attraversa una barriera di memoria il compilatore non può fare affidamento su un valore memorizzato nella cache. Bloccare / sbloccare un mutex sarebbe una tale barriera di memoria. Poiché la variabile è statica e quindi non visibile al di fuori del file corrente, il compilatore può presumere che una funzione non possa mai modificarne il valore, a meno che non si passi un riferimento a quella variabile alla funzione. Così volatile è fortemente consigliato qui in ogni caso.
Mecki,

47

Controlla qui:

Nota: Ovviamente, questo è un semplice esempio che spiega proprio come impostare un CtrlCgestore, ma come sempre ci sono delle regole che devono essere rispettate al fine di non rompere qualcos'altro. Si prega di leggere i commenti qui sotto.

Il codice di esempio dall'alto:

#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
}

2
@Derrick D'accordo, int mainè una cosa giusta, ma gcce altri compilatori lo compilano in programmi correttamente eseguiti dagli anni '90. Spiegato abbastanza bene qui: eskimo.com/~scs/readings/voidmain.960823.html - è fondamentalmente una "caratteristica", la prendo così.
icyrock.com,

1
@ icyrock.com: tutto vero (per quanto riguarda void main () in C), ma quando pubblichi un post pubblicamente è probabilmente anche possibile evitare del tutto il dibattito usando int main () per non distogliere l'attenzione dal punto principale.
Clifford,

21
C'è un grosso difetto in questo. Non è possibile utilizzare in modo sicuro printf all'interno del contenuto di un gestore di segnali. È una violazione della sicurezza del segnale asincrono. Questo perché printf non è rientrante. Cosa succede se il programma era nel mezzo dell'utilizzo di printf quando è stato premuto Ctrl-C e il gestore del segnale inizia a usarlo contemporaneamente? Suggerimento: probabilmente si romperà. write e fwrite sono gli stessi da usare in questo contesto.
Dylan Lukes,

2
@ icyrock.com: fare qualcosa di complicato in un gestore di segnale provocherà mal di testa. Soprattutto utilizzando il sistema io.
Martin York,

2
@stacker Grazie - Penso che ne valga la pena alla fine. Se qualcuno dovesse imbattersi in questo codice in futuro, meglio averlo nel modo giusto il più possibile, indipendentemente dall'argomento della domanda.
icyrock.com,

30

Addendum relativo alle piattaforme UN * X.

Secondo la signal(2)pagina man su GNU / Linux, il comportamento di signalnon è portatile come il comportamento di sigaction:

Il comportamento di signal () varia tra le versioni UNIX e ha anche variato storicamente tra le diverse versioni di Linux. Evita il suo uso: usa invece sigaction (2).

Su System V, il sistema non ha bloccato la consegna di ulteriori istanze del segnale e la consegna di un segnale ripristinava il gestore a quello predefinito. In BSD la semantica è cambiata.

La seguente variante della risposta precedente di Dirk Eddelbuettel utilizza sigactioninvece di 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
    }
}


14

Oppure puoi mettere il terminale in modalità raw, in questo modo:

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);

Ora dovrebbe essere possibile leggere Ctrl+ Csequenze di tasti usando fgetc(stdin). Fai attenzione, però, perché non puoi più usare Ctrl+ Z, Ctrl+ Q, Ctrl+ S, ecc. Normalmente.


11

Imposta una trap (puoi intercettare più segnali con un solo gestore):

segnale (SIGQUIT, my_handler);
segnale (SIGINT, my_handler);

Gestisci il segnale come preferisci, ma fai attenzione a limitazioni e aspetti:

void my_handler (int sig)
{
  / * Il tuo codice qui. * /
}

8

@Peter Varo ha aggiornato la risposta di Dirk, ma Dirk ha respinto la modifica. Ecco la nuova risposta di Peter:

Sebbene lo snippet sopra riportato sia corretto ad esempio, si dovrebbero usare i tipi più moderni e le garanzie fornite dalle norme successive, se possibile. Pertanto, ecco un'alternativa più sicura e moderna per coloro che cercano il e implementazione conforme:

#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;
}

Standard C11: 7.14§2 L'intestazione <signal.h>dichiara un tipo ... sig_atomic_tche è il tipo intero (possibilmente volatile) di un oggetto a cui è possibile accedere come entità atomica, anche in presenza di interruzioni asincrone.

Inoltre:

Standard C11: 7.14.1.1§5 Se il segnale si verifica diverso da come risultato della chiamata alla funzione aborto raise, il comportamento non è definito se il gestore del segnale si riferisce a qualsiasi oggetto con statico durata della memorizzazione del thread che non sia un oggetto atomico senza blocco altro che assegnando un valore a un oggetto dichiarato come volatile sig_atomic_t...


Non capisco il (void)_;... Qual è il suo scopo? È così che il compilatore non avverte di una variabile non utilizzata?
Luis Paulo,


Avevo appena trovato quella risposta e stavo per incollare quel link qui! Grazie :)
Luis Paulo,

5

Per quanto riguarda le risposte esistenti, tenere presente che la gestione del segnale dipende dalla piattaforma. Win32 ad esempio gestisce molti meno segnali rispetto ai sistemi operativi POSIX; vedi qui . Mentre SIGINT è dichiarato in signal.h su Win32, vedere la nota nella documentazione che spiega che non farà ciò che ci si potrebbe aspettare.


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;
}

La funzione sig_handler verifica se il valore dell'argomento passato è uguale a SIGINT, quindi viene eseguito printf.


Non chiamare mai routine di I / O in un gestore di segnali.
jwdonahue,

2

Questo stampa solo prima di uscire.

#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);
}

2
Non chiamare mai l'I / O in un gestore di segnale.
jwdonahue,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.