Catch Ctrl-C en C

158

Comment attraper Ctrl+ Cen C?

Feldor
la source
5
Il n'y a rien de tel que la capture de signaux en C ... ou du moins c'est ce que j'ai pensé jusqu'à ce que je lis la norme C99. Il s'avère que la gestion du signal est définie en C mais que Ctrl-C n'est pas mandaté pour produire un signal spécifique ou un signal du tout. Selon votre plate-forme, cela peut être impossible.
JeremyP
1
La gestion du signal dépend principalement de la mise en œuvre. Sur les plates-formes * nix, utilisez <signal.h>, et si vous êtes sur OSX, vous pouvez profiter de GCD pour rendre les choses encore plus faciles ~.
Dylan Lukes

Réponses:

205

Avec un gestionnaire de signaux.

Voici un exemple simple de retournement d'un boolutilisé dans main():

#include <signal.h>

static volatile int keepRunning = 1;

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

// ...

int main(void) {

   signal(SIGINT, intHandler);

   while (keepRunning) { 
      // ...

Edit en juin 2017 : à qui cela peut concerner, en particulier ceux qui ont une envie insatiable de modifier cette réponse. Écoutez, j'ai écrit cette réponse il y a sept ans. Oui, les normes linguistiques changent. Si vous devez vraiment améliorer le monde, veuillez ajouter votre nouvelle réponse, mais laissez la mienne telle quelle. Comme la réponse porte mon nom, je préférerais qu'elle contienne aussi mes mots. Je vous remercie.

Dirk Eddelbuettel
la source
2
Mentionnons que nous devons #include <signal.h> pour que cela fonctionne!
kristianlm le
13
statique Cela devrait être bool volatile keepRunning = true;sûr à 100%. Le compilateur est libre de mettre keepRunningen cache dans un registre et le volatile l'empêchera. En pratique, il peut très probablement également fonctionner sans le mot clé volatile lorsque la boucle while appelle au moins une fonction non en ligne.
Johannes Overmann
2
@DirkEddelbuettel Je suis corrigé, je pensais que mes améliorations refléteraient davantage vos intentions d'origine, désolé si cela ne s'est pas produit. Quoi qu'il en soit, le plus gros problème est que, puisque votre réponse essaie d'être suffisamment générique, et parce que l'OMI, elle devrait fournir un extrait de code qui fonctionne également sous des interruptions asynchrones: j'utiliserais sig_atomic_tou atomic_booltaperais là-bas. Je viens de manquer celui-là. Maintenant, puisque nous parlons: voudriez-vous que j'annule ma dernière modification? Pas de rancune là-bas, ce serait parfaitement compréhensible de votre point de vue :)
Peter Varo
2
C'est bien mieux!
Dirk Eddelbuettel
2
@JohannesOvermann Non pas que je veuille pinailler, mais à proprement parler, peu importe si le code appelle des fonctions non en ligne ou non, car ce n'est que lors du franchissement d'une barrière mémoire que le compilateur n'est pas autorisé à s'appuyer sur une valeur mise en cache. Verrouiller / déverrouiller un mutex serait une telle barrière de mémoire. Comme la variable est statique et donc non visible en dehors du fichier en cours, le compilateur peut supposer qu'une fonction ne peut jamais changer sa valeur, sauf si vous passez une référence à cette variable à la fonction. Donc volatile est ici fortement conseillé dans tous les cas.
Mecki du
47

Vérifiez ici:

Note: Évidemment, ceci est un exemple simple expliquant simplement comment mettre en place un CtrlCgestionnaire, mais comme toujours il y a des règles qui doivent être respectées afin de ne pas casser autre chose. Veuillez lire les commentaires ci-dessous.

L'exemple de code ci-dessus:

#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
la source
2
@Derrick D'accord, int mainest une bonne chose, mais gccet d'autres compilateurs le compilent en programmes fonctionnant correctement depuis les années 1990. Expliqué assez bien ici: eskimo.com/~scs/readings/voidmain.960823.html - c'est fondamentalement une "fonctionnalité", je le prends comme ça.
icyrock.com
1
@ icyrock.com: Tout est très vrai (en ce qui concerne void main () en C), mais lors de la publication publique, il est probablement préférable d'éviter complètement le débat en utilisant int main () de peur que cela ne détourne l'attention du point principal.
Clifford
21
Il y a un énorme défaut à cela. Vous ne pouvez pas utiliser en toute sécurité printf dans le contenu d'un gestionnaire de signaux. C'est une violation de la sécurité des signaux asynchrones. C'est parce que printf n'est pas réentrant. Que se passe-t-il si le programme était en train d'utiliser printf lorsque vous appuyez sur Ctrl-C et que votre gestionnaire de signaux commence à l'utiliser en même temps? Indice: il se cassera probablement. write et fwrite sont les mêmes à utiliser dans ce contexte.
Dylan Lukes
2
@ icyrock.com: Faire quelque chose de compliqué dans un gestionnaire de signaux va causer des maux de tête. Surtout en utilisant le système io.
Martin York
2
@stacker Merci - Je pense que ça vaut le coup à la fin. Si quelqu'un tombe sur ce code à l'avenir, mieux vaut le faire le plus possible, quel que soit le sujet de la question.
icyrock.com
30

Addendum concernant les plates-formes UN * X.

Selon la signal(2)page de manuel sur GNU / Linux, le comportement de signaln'est pas aussi portable que celui de sigaction:

Le comportement de signal () varie selon les versions UNIX et a également varié historiquement entre les différentes versions de Linux. Évitez son utilisation: utilisez plutôt sigaction (2).

Sur le système V, le système ne bloquait pas la livraison d'autres instances du signal et la livraison d'un signal réinitialisait le gestionnaire à la valeur par défaut. Dans BSD, la sémantique a changé.

La variante suivante de la réponse précédente de Dirk Eddelbuettel utilise à la sigactionplace 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.
la source
14

Ou vous pouvez mettre le terminal en mode brut, comme ceci:

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

Maintenant, il devrait être possible de lire Ctrl+ les Cfrappes avec fgetc(stdin). Attention cependant à utiliser ceci car vous ne pouvez pas non plus Ctrl+ Z, Ctrl+ Q, Ctrl+ S, etc. comme normalement.

Walter
la source
11

Configurez un piège (vous pouvez intercepter plusieurs signaux avec un seul gestionnaire):

signal (SIGQUIT, mon_handler);
signal (SIGINT, mon_handler);

Gérez le signal comme vous le souhaitez, mais soyez conscient des limites et des pièges:

void my_handler (int sig)
{
  / * Votre code ici. * /
}
Paul Beckingham
la source
8

@Peter Varo a mis à jour la réponse de Dirk, mais Dirk a rejeté le changement. Voici la nouvelle réponse de Peter:

Bien que l'extrait de code ci-dessus soit correct Par exemple, on devrait utiliser les types les plus modernes et les garanties fournies par les normes ultérieures si possible. Par conséquent, voici une alternative plus sûre et moderne pour ceux qui recherchent le et mise en œuvre 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;
}

C11 Standard: 7.14§2 L'en-tête <signal.h>déclare un type ... sig_atomic_tqui est le type entier (éventuellement volatile) d'un objet auquel on peut accéder en tant qu'entité atomique, même en présence d'interruptions asynchrones.

En outre:

C11 Norme: 7.14.1.1§5 Si le signal se produit autrement qu'à la suite de l'appel de la fonction abortou raise, le comportement est indéfini si le gestionnaire de signal fait référence à un objet avec staticune durée de stockage de thread ou qui n'est pas un objet atomique sans verrouillage autre qu'en attribuant une valeur à un objet déclaré comme volatile sig_atomic_t...

MCCCS
la source
Je ne comprends pas le (void)_;... Quel est son but? Est-ce que le compilateur n'avertit pas d'une variable inutilisée?
Luis Paulo
1
@LuisPaulo Oui à la deuxième question.
MCCCS le
Je venais de trouver cette réponse et j'étais sur le point de coller ce lien ici! Thanks :)
Luis Paulo
5

En ce qui concerne les réponses existantes, notez que la gestion du signal dépend de la plate-forme. Win32, par exemple, gère beaucoup moins de signaux que les systèmes d'exploitation POSIX; voir ici . Alors que SIGINT est déclaré dans signaux.h sur Win32, consultez la note dans la documentation qui explique qu'il ne fera pas ce que vous pourriez attendre.

Clifford
la source
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 fonction sig_handler vérifie si la valeur de l'argument passé est égale au SIGINT, puis le printf est exécuté.

Amour Bisaria
la source
N'appelez jamais de routines d'E / S dans un gestionnaire de signaux.
jwdonahue
2

Ceci s'imprime juste avant de quitter.

#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);
}
alémol
la source
2
N'appelez jamais les E / S dans un gestionnaire de signaux.
jwdonahue