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():
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.
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.
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>voidINThandler(int);int main(void){
signal(SIGINT,INThandler);while(1)
pause();return0;}voidINThandler(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}
@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:
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.
@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 c89Par 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 lec99 et c11 mise en œuvre conforme:
#include<signal.h>#include<stdlib.h>#include<stdio.h>staticvolatilesig_atomic_t keep_running =1;staticvoid 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...
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.
#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 processwhile(1)
sleep(1);return0;}
La fonction sig_handler vérifie si la valeur de l'argument passé est égale au SIGINT, puis le printf est exécuté.
Réponses:
Avec un gestionnaire de signaux.
Voici un exemple simple de retournement d'un
bool
utilisé dansmain()
: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.
la source
bool volatile keepRunning = true;
sûr à 100%. Le compilateur est libre de mettrekeepRunning
en 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.sig_atomic_t
ouatomic_bool
taperais 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 :)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:
la source
int main
est une bonne chose, maisgcc
et 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.Addendum concernant les plates-formes UN * X.
Selon la
signal(2)
page de manuel sur GNU / Linux, le comportement designal
n'est pas aussi portable que celui desigaction
: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
sigaction
place designal
:la source
volatile sig_atomic_t
être bien définiOu vous pouvez mettre le terminal en mode brut, comme ceci:
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.la source
Configurez un piège (vous pouvez intercepter plusieurs signaux avec un seul gestionnaire):
Gérez le signal comme vous le souhaitez, mais soyez conscient des limites et des pièges:
la source
@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 c89Par 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 lec99 et c11 mise en œuvre conforme:
En outre:
la source
(void)_;
... Quel est son but? Est-ce que le compilateur n'avertit pas d'une variable inutilisée?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.
la source
La fonction sig_handler vérifie si la valeur de l'argument passé est égale au SIGINT, puis le printf est exécuté.
la source
Ceci s'imprime juste avant de quitter.
la source