Come si fa a catturare Ctrl+ Cin C?
Come si fa a catturare Ctrl+ Cin C?
Risposte:
Con un gestore di segnale.
Ecco un semplice esempio lanciando un bool
usato 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.
bool volatile keepRunning = true;
sicuro al 100%. Il compilatore è libero di memorizzare keepRunning
nella 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.
sig_atomic_t
o atomic_bool
digiterei 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 :)
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
}
int main
è una cosa giusta, ma gcc
e 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ì.
Addendum relativo alle piattaforme UN * X.
Secondo la signal(2)
pagina man su GNU / Linux, il comportamento di signal
non è 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 sigaction
invece 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
}
}
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.
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. * / }
@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 C89ad 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 ilc99 e c11 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_t
che è 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
abort
oraise
, il comportamento non è definito se il gestore del segnale si riferisce a qualsiasi oggetto constatic
o durata della memorizzazione del thread che non sia un oggetto atomico senza blocco altro che assegnando un valore a un oggetto dichiarato comevolatile sig_atomic_t
...
(void)_;
... Qual è il suo scopo? È così che il compilatore non avverte di una variabile non utilizzata?
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.
#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.
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);
}