Fangen Sie Strg-C in C.


158

Wie fängt man Ctrl+ Cin C?


5
In C gibt es keine Signale ... oder zumindest dachte ich, bis ich den C99-Standard gelesen habe. Es stellt sich heraus, dass in C eine Signalbehandlung definiert ist, aber Strg-C ist nicht verpflichtet, ein bestimmtes Signal oder überhaupt ein Signal zu erzeugen. Abhängig von Ihrer Plattform ist dies möglicherweise nicht möglich.
JeremyP

1
Die Signalverarbeitung hängt hauptsächlich von der Implementierung ab. Verwenden Sie auf * nix-Plattformen <signal.h>, und wenn Sie unter OSX arbeiten, können Sie GCD nutzen, um die Dinge noch einfacher zu machen ~.
Dylan Lukes

Antworten:


205

Mit einem Signalhandler.

Hier ist ein einfaches Beispiel für das Umdrehen eines boolverwendeten in main():

#include <signal.h>

static volatile int keepRunning = 1;

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

// ...

int main(void) {

   signal(SIGINT, intHandler);

   while (keepRunning) { 
      // ...

Bearbeiten im Juni 2017 : Wen es betrifft, insbesondere diejenigen mit einem unersättlichen Drang, diese Antwort zu bearbeiten. Schau, ich habe diese Antwort vor sieben Jahren geschrieben. Ja, Sprachstandards ändern sich. Wenn Sie die Welt wirklich verbessern müssen, fügen Sie bitte Ihre neue Antwort hinzu, aber lassen Sie meine so wie sie ist. Da die Antwort meinen Namen trägt, würde ich es vorziehen, wenn sie auch meine Worte enthält. Danke dir.


2
Lassen Sie uns erwähnen, dass wir <include <signal.h> einschließen müssen, damit dies funktioniert!
Kristianlm

13
statisch Es sollte bool volatile keepRunning = true;100% sicher sein. Der Compiler kann frei keepRunningin einem Register zwischengespeichert werden, und das Flüchtige verhindert dies. In der Praxis funktioniert es höchstwahrscheinlich auch ohne das flüchtige Schlüsselwort, wenn die while-Schleife mindestens eine Nicht-Inline-Funktion aufruft.
Johannes Overmann

2
@DirkEddelbuettel Ich stehe korrigiert da, ich dachte, meine Verbesserungen werden Ihre ursprünglichen Absichten mehr widerspiegeln, sorry, wenn das nicht passiert ist. Das größere Problem ist jedenfalls, dass Ihre Antwort, da sie versucht, allgemein genug zu sein, und IMO ein Snippet bereitstellen sollte, das auch unter asynchronen Interrupts funktioniert: Ich würde sie dort entweder verwenden sig_atomic_toder atomic_booltippen. Ich habe das gerade verpasst. Nun, da wir uns unterhalten: Möchten Sie, dass ich meine letzte Bearbeitung zurücksetze? Keine harten Gefühle dort wäre es aus Ihrer Sicht vollkommen verständlich :)
Peter Varo

2
Das ist viel besser!
Dirk Eddelbuettel

2
@JohannesOvermann Nicht, dass ich nitpicken möchte, aber genau genommen spielt es keine Rolle, ob der Code Nicht-Inline-Funktionen aufruft oder nicht, da der Compiler nur beim Überqueren einer Speicherbarriere nicht auf einen zwischengespeicherten Wert angewiesen ist. Das Sperren / Entsperren eines Mutex wäre eine solche Speicherbarriere. Da die Variable statisch ist und daher außerhalb der aktuellen Datei nicht sichtbar ist, kann der Compiler davon ausgehen, dass eine Funktion ihren Wert niemals ändern kann, es sei denn, Sie übergeben der Funktion einen Verweis auf diese Variable. Daher wird hier auf jeden Fall dringend von Volatilität abgeraten.
Mecki

47

Überprüfe hier:

Hinweis: Offensichtlich ist dies ein einfaches Beispiel zu erklären nur , wie ein einzurichtenCtrlC Handler, aber wie immer gibt es Regeln, die Notwendigkeit , um befolgt werden , nicht etwas anderes zu brechen. Bitte lesen Sie die Kommentare unten.

Der Beispielcode von oben:

#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
}

2
@Derrick Zustimmen, int mainist eine richtige Sache, aber gccund andere Compiler kompilieren dies zu einem korrekt laufenden Programm seit den 1990er Jahren. Hier ziemlich gut erklärt: eskimo.com/~scs/readings/voidmain.960823.html - es ist im Grunde eine "Funktion", ich nehme es so.
icyrock.com

1
@ icyrock.com: Alles sehr wahr (in Bezug auf void main () in C), aber wenn Sie öffentlich posten, ist es wahrscheinlich auch gut, die Debatte durch die Verwendung von int main () zu vermeiden, damit sie nicht vom Hauptpunkt ablenkt.
Clifford

21
Darin liegt ein großer Fehler. Sie können printf nicht sicher im Inhalt eines Signalhandlers verwenden. Dies ist eine Verletzung der Sicherheit asynchroner Signale. Dies liegt daran, dass printf nicht wiedereintrittsfähig ist. Was passiert, wenn das Programm gerade printf verwendet hat, als Strg-C gedrückt wurde, und Ihr Signalhandler es gleichzeitig verwendet? Hinweis: Es wird wahrscheinlich brechen. Schreiben und Schreiben sind in diesem Zusammenhang gleich.
Dylan Lukes

2
@ icyrock.com: Wenn Sie in einem Signalhandler etwas Kompliziertes tun, kann dies zu Kopfschmerzen führen. Besonders mit dem io System.
Martin York

2
@stacker Danke - ich denke es lohnt sich am Ende. Wenn jemand in Zukunft über diesen Code stolpert, ist es besser, ihn so gut wie möglich richtig zu machen, unabhängig vom Thema der Frage.
icyrock.com

30

Nachtrag zu UN * X-Plattformen.

Laut der signal(2)Manpage unter GNU / Linux ist das Verhalten von signalnicht so portabel wie das Verhalten von sigaction:

Das Verhalten von signal () variiert je nach UNIX-Version und hat sich auch historisch zwischen verschiedenen Linux-Versionen geändert. Vermeiden Sie die Verwendung: Verwenden Sie stattdessen Sigaction (2).

Auf System V blockierte das System die Zustellung weiterer Instanzen des Signals nicht, und die Zustellung eines Signals würde den Handler auf den Standardwert zurücksetzen. In BSD hat sich die Semantik geändert.

Die folgende Variation der vorherigen Antwort von Dirk Eddelbuettel verwendet sigactionanstelle von 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
    }
}


14

Oder Sie können das Terminal wie folgt in den Raw-Modus versetzen:

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

Jetzt sollte es möglich sein, Ctrl+ CTastenanschläge mit zu lesen fgetc(stdin). Passen Sie auf, dass Sie dies nicht verwenden , da Sie Ctrl+ Z, Ctrl+ Q, Ctrl+ Susw. auch nicht mehr normal mögen können.


11

Richten Sie eine Falle ein (Sie können mehrere Signale mit einem Handler abfangen):

Signal (SIGQUIT, my_handler);
Signal (SIGINT, my_handler);

Behandeln Sie das Signal so, wie Sie möchten, aber beachten Sie die Einschränkungen und Fallstricke:

void my_handler (int sig)
{
  / * Dein Code hier. * /
}}

8

@ Peter Varo hat Dirks Antwort aktualisiert, aber Dirk hat die Änderung abgelehnt. Hier ist die neue Antwort von Peter:

Obwohl das obige Snippet korrekt ist Beispielsweise sollte man nach Möglichkeit die moderneren Typen und Garantien verwenden, die durch die späteren Normen bereitgestellt werden. Daher ist hier eine sicherere und modernere Alternative für diejenigen, die nach dem suchen und konforme Umsetzung:

#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 Der Header <signal.h>deklariert einen Typ ..., sig_atomic_tder der (möglicherweise flüchtig qualifizierte) Integer-Typ eines Objekts ist, auf den auch bei asynchronen Interrupts als atomare Entität zugegriffen werden kann.

Außerdem:

C11 Standard: 7.14.1.1§5 Wenn das Signal nicht als Ergebnis des Aufrufs der Funktion abortoder auftritt raise, ist das Verhalten undefiniert, wenn der Signalhandler auf ein Objekt mit staticoder eine Thread-Speicherdauer verweist, das kein anderes sperrenfreies atomares Objekt ist als indem Sie einem als volatile sig_atomic_t... deklarierten Objekt einen Wert zuweisen.


Ich verstehe unser nicht (void)_;... Was ist ihr Zweck? Ist es so, dass der Compiler nicht vor einer nicht verwendeten Variablen warnt?
Luis Paulo


Ich hatte gerade diese Antwort gefunden und wollte diesen Link hier einfügen! Vielen Dank :)
Luis Paulo

5

Beachten Sie in Bezug auf vorhandene Antworten, dass die Signalverarbeitung plattformabhängig ist. Win32 verarbeitet beispielsweise weit weniger Signale als POSIX-Betriebssysteme. siehe hier . Während SIGINT in wins.h unter Win32 deklariert ist, lesen Sie den Hinweis in der Dokumentation, in dem erklärt wird, dass es nicht das tut, was Sie vielleicht erwarten.


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

Die Funktion sig_handler prüft, ob der Wert des übergebenen Arguments gleich dem SIGINT ist, und führt dann printf aus.


Rufen Sie niemals E / A-Routinen in einem Signalhandler auf.
Jwdonahue

2

Dies wird nur vor dem Beenden gedruckt.

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

2
Rufen Sie niemals E / A in einem Signalhandler an.
Jwdonahue
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.