¿Cómo se atrapa Ctrl+ Cen C?
¿Cómo se atrapa Ctrl+ Cen C?
Respuestas:
Con un controlador de señal.
Aquí hay un ejemplo simple volteando un bool
usado en main()
:
#include <signal.h>
static volatile int keepRunning = 1;
void intHandler(int dummy) {
keepRunning = 0;
}
// ...
int main(void) {
signal(SIGINT, intHandler);
while (keepRunning) {
// ...
Edite en junio de 2017 : a quien corresponda, particularmente a aquellos con una necesidad insaciable de editar esta respuesta. Mira, escribí esta respuesta hace siete años. Sí, los estándares del idioma cambian. Si realmente debe mejorar el mundo, agregue su nueva respuesta pero deje la mía tal como está. Como la respuesta tiene mi nombre, prefiero que también contenga mis palabras. Gracias.
bool volatile keepRunning = true;
ser 100% seguro. El compilador es libre de almacenar keepRunning
en caché en un registro y el volátil evitará esto. En la práctica, lo más probable es que también funcione sin la palabra clave volátil cuando el ciclo while llama al menos a una función no en línea.
sig_atomic_t
o atomic_bool
escribiría allí. Acabo de perder esa. Ahora, ya que estamos hablando: ¿quieres que revierta mi última edición? No hay resentimientos allí, sería perfectamente comprensible desde su punto de vista :)
Chequea aquí:
Nota: Obviamente, esto es un ejemplo sencillo de explicar simplemente cómo configurar un CtrlCcontrolador, pero como siempre hay reglas que deben ser obedecidas con el fin de no romper algo más. Por favor lea los comentarios a continuación.
El código de muestra de arriba:
#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
es algo apropiado, pero gcc
y otros compiladores compilan esto en programas que se ejecutan correctamente desde la década de 1990. Explicado bastante bien aquí: eskimo.com/~scs/readings/voidmain.960823.html - es básicamente una "característica", lo tomo así.
Anexo sobre las plataformas UN * X.
Según la signal(2)
página de manual de GNU / Linux, el comportamiento de signal
no es tan portátil como el comportamiento de sigaction
:
El comportamiento de la señal () varía según las versiones de UNIX y también ha variado históricamente entre las diferentes versiones de Linux. Evite su uso: use sigaction (2) en su lugar.
En el Sistema V, el sistema no bloqueó la entrega de más instancias de la señal y la entrega de una señal restablecería el controlador al predeterminado. En BSD la semántica cambió.
La siguiente variación de la respuesta anterior de Dirk Eddelbuettel utiliza en sigaction
lugar de 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
}
}
O puede poner el terminal en modo sin formato, así:
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);
Ahora debería ser posible leer Ctrl+ Cpulsaciones de teclas usando fgetc(stdin)
. Sin embargo, tenga cuidado al usar esto porque ya no puede Ctrl+ Z, Ctrl+ Q, Ctrl+ S, etc., como normalmente tampoco.
Configure una trampa (puede atrapar varias señales con un controlador):
señal (SIGQUIT, my_handler); señal (SIGINT, my_handler);
Maneja la señal como quieras, pero ten en cuenta las limitaciones y las trampas:
vacío my_handler (int sig) { / * Su código aquí. * / }
@Peter Varo actualizó la respuesta de Dirk, pero Dirk rechazó el cambio. Aquí está la nueva respuesta de Peter:
Aunque el fragmento anterior es correcto c89Por ejemplo, uno debería usar los tipos más modernos y las garantías proporcionadas por los estándares posteriores si es posible. Por lo tanto, aquí hay una alternativa más segura y moderna para aquellos que buscanc99 y c11 implementación 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;
}
Estándar C11: 7.14§2 El encabezado
<signal.h>
declara un tipo ...sig_atomic_t
que es el tipo entero (posiblemente calificado como volátil) de un objeto al que se puede acceder como una entidad atómica, incluso en presencia de interrupciones asincrónicas.
Además:
Estándar C11: 7.14.1.1§5 Si la señal se produce de otra manera que como resultado de llamar a la función
abort
oraise
, el comportamiento no está definido si el manejador de señal se refiere a cualquier objeto constatic
o duración de almacenamiento de subprocesos que no sea un objeto atómico sin bloqueo otro que asignando un valor a un objeto declarado comovolatile sig_atomic_t
...
(void)_;
... ¿Cuál es su propósito? ¿Es para que el compilador no advierta sobre una variable no utilizada?
Con respecto a las respuestas existentes, tenga en cuenta que el manejo de la señal depende de la plataforma. Win32, por ejemplo, maneja muchas menos señales que los sistemas operativos POSIX; ver aquí . Mientras SIGINT se declara en signal.h en Win32, consulte la nota en la documentación que explica que no hará lo que podría esperar.
#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 función sig_handler verifica si el valor del argumento pasado es igual a SIGINT, luego se ejecuta printf.
Esto solo imprime antes de salir.
#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);
}