Captura Ctrl-C en C

158

¿Cómo se atrapa Ctrl+ Cen C?

Feldor
fuente
55
No hay tal cosa como captar señales en C ... o al menos eso pensé hasta que leí el estándar C99. Resulta que hay un manejo de señal definido en C pero Ctrl-C no está obligado a producir ninguna señal específica o una señal en absoluto. Dependiendo de su plataforma, esto podría ser imposible.
JeremyP
1
El manejo de la señal depende principalmente de la implementación. En plataformas * nix, use <signal.h>, y si está en OSX puede aprovechar GCD para hacer las cosas aún más fáciles ~.
Dylan Lukes

Respuestas:

205

Con un controlador de señal.

Aquí hay un ejemplo simple volteando un boolusado 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.

Dirk Eddelbuettel
fuente
2
¡Mencionemos que necesitamos #incluir <signal.h> para que esto funcione!
kristianlm
13
estático Debe bool volatile keepRunning = true;ser 100% seguro. El compilador es libre de almacenar keepRunningen 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.
Johannes Overmann
2
@DirkEddelbuettel Estoy corregido, pensé que mis mejoras reflejarían más tus intenciones originales, lo siento si eso no sucedió. De todos modos, el mayor problema es que, dado que su respuesta intenta ser lo suficientemente genérica, y debido a IMO, debería proporcionar un fragmento que también funciona bajo interrupciones asincrónicas: usaría sig_atomic_to atomic_boolescribirí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 :)
Peter Varo
2
¡Eso es mucho mejor!
Dirk Eddelbuettel
2
@JohannesOvermann No es que quiera jugar un poco, pero estrictamente hablando, no importa si el código llama a funciones no en línea o no, ya que solo al cruzar una barrera de memoria el compilador no puede confiar en un valor almacenado en caché. Bloquear / desbloquear un mutex sería una barrera de memoria. Como la variable es estática y, por lo tanto, no es visible fuera del archivo actual, el compilador puede asumir con seguridad que una función nunca puede cambiar su valor, a menos que pase una referencia a esa variable a la función. Tan volátil se recomienda encarecidamente aquí en cualquier caso.
Mecki
47

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
}
icyrock.com
fuente
2
@Derrick Agree, int maines algo apropiado, pero gccy 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í.
icyrock.com
1
@ icyrock.com: Todo es muy cierto (con respecto a void main () en C), pero cuando se publica públicamente, probablemente sea mejor evitar el debate por completo usando int main () para que no distraiga del punto principal.
Clifford
21
Hay un gran defecto en esto. No puede usar printf de forma segura dentro del contenido de un controlador de señal. Es una violación de la seguridad de la señal asincrónica. Esto se debe a que printf no es reentrante. ¿Qué sucede si el programa estaba en el medio de usar printf cuando se presionó Ctrl-C, y su controlador de señal comienza a usarlo al mismo tiempo? Sugerencia: es probable que se rompa. write y fwrite son los mismos para usar en este contexto.
Dylan Lukes
2
@ icyrock.com: Hacer cualquier cosa complicada en un controlador de señal va a causar dolores de cabeza. Especialmente usando el sistema io.
Martin York
2
@stacker Gracias, creo que vale la pena al final. Si alguien se topa con este código en el futuro, es mejor tenerlo lo más correcto posible, independientemente del tema de la pregunta.
icyrock.com
30

Anexo sobre las plataformas UN * X.

Según la signal(2)página de manual de GNU / Linux, el comportamiento de signalno 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 sigactionlugar 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
    }
}
Filip J.
fuente
14

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.

Walter
fuente
11

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

@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 Por 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 buscan y 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_tque 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 aborto raise, el comportamiento no está definido si el manejador de señal se refiere a cualquier objeto con statico duración de almacenamiento de subprocesos que no sea un objeto atómico sin bloqueo otro que asignando un valor a un objeto declarado como volatile sig_atomic_t...

MCCCS
fuente
No entiendo el (void)_;... ¿Cuál es su propósito? ¿Es para que el compilador no advierta sobre una variable no utilizada?
Luis Paulo
1
@LuisPaulo Sí a la segunda pregunta.
MCCCS
¡Acababa de encontrar esa respuesta y estaba a punto de pegar ese enlace aquí! Gracias :)
Luis Paulo
5

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.

Clifford
fuente
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 función sig_handler verifica si el valor del argumento pasado es igual a SIGINT, luego se ejecuta printf.

Amor bisaria
fuente
Nunca llame a las rutinas de E / S en un controlador de señal.
jwdonahue
2

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);
}
alemol
fuente
2
Nunca llame a E / S en un controlador de señal.
jwdonahue