Złap Ctrl + C w C

158

Jak się łapie Ctrl + Cw C?

Feldor
źródło
5
Nie ma czegoś takiego jak łapanie sygnałów w C .... a przynajmniej tak myślałem, dopóki nie przeczytałem standardu C99. Okazuje się, że obsługa sygnału jest zdefiniowana w C, ale Ctrl-C nie jest upoważnione do wytwarzania żadnego określonego sygnału lub sygnału w ogóle. W zależności od platformy może to być niemożliwe.
JeremyP
1
Obsługa sygnału jest w większości zależna od implementacji. Na platformach * nix użyj <signal.h>, a jeśli korzystasz z OSX, możesz skorzystać z GCD, aby wszystko było jeszcze łatwiejsze ~.
Dylan Lukes

Odpowiedzi:

205

Z obsługą sygnału.

Oto prosty przykład odwracający boolużywany w main():

#include <signal.h>

static volatile int keepRunning = 1;

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

// ...

int main(void) {

   signal(SIGINT, intHandler);

   while (keepRunning) { 
      // ...

Edytuj w czerwcu 2017 r . : Kogo to może dotyczyć, zwłaszcza tych, którzy mają nienasyconą potrzebę edycji tej odpowiedzi. Słuchaj, napisałem tę odpowiedź siedem lat temu. Tak, zmieniają się standardy językowe. Jeśli naprawdę musisz ulepszyć świat, dodaj swoją nową odpowiedź, ale moją pozostaw taką, jaka jest. Ponieważ odpowiedź zawiera moje imię, wolałbym, aby zawierała również moje słowa. Dziękuję Ci.

Dirk Eddelbuettel
źródło
2
Wspomnijmy, że aby to zadziałało, musimy #include <signal.h>!
kristianlm
13
statyczny Powinien być bool volatile keepRunning = true;w 100% bezpieczny. Kompilator może swobodnie buforować keepRunningw rejestrze, a ulotność temu zapobiegnie. W praktyce najprawdopodobniej może również działać bez słowa kluczowego volatile, gdy pętla while wywołuje co najmniej jedną funkcję nieliniową.
Johannes Overmann
2
@DirkEddelbuettel Stoję poprawiony, myślałem, że moje ulepszenia będą bardziej odzwierciedlać twoje pierwotne zamiary, przepraszam, jeśli tak się nie stało. W każdym razie większym problemem jest to, że skoro twoja odpowiedź stara się być wystarczająco ogólna, a ponieważ IMO powinna dostarczyć fragment, który również działa w przypadku przerwań asynchronicznych: albo użyłbym tam sig_atomic_talbo atomic_boolwpiszę. Właśnie to przegapiłem. Skoro już rozmawiamy: czy mam wycofać moją ostatnią edycję? Żadnych urazy, byłoby to całkowicie zrozumiałe z twojego punktu widzenia :)
Peter Varo
2
To jest o wiele lepsze!
Dirk Eddelbuettel
2
@JohannesOvermann Nie chodzi o to, że chcę się dziwić, ale mówiąc ściśle, nie ma znaczenia, czy kod wywołuje funkcje nie-wbudowane, czy nie, ponieważ tylko podczas przekraczania bariery pamięci kompilator nie może polegać na wartości z pamięci podręcznej. Blokowanie / odblokowywanie muteksu byłoby taką barierą pamięci. Ponieważ zmienna jest statyczna, a zatem niewidoczna poza bieżącym plikiem, kompilator może bezpiecznie założyć, że funkcja nie może nigdy zmienić swojej wartości, chyba że przekażesz do niej odniesienie do tej zmiennej. Tak lotne jest tutaj zdecydowanie zalecane w każdym przypadku.
Mecki,
47

Sprawdź tutaj:

Uwaga: Oczywiście, jest to prosty przykład wyjaśniający tylko jak uruchamiamyCtrlC program obsługi, ale jak zawsze istnieją zasady, które muszą być przestrzegane, aby nie złamać coś innego. Przeczytaj poniższe komentarze.

Przykładowy kod z góry:

#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
źródło
2
@Derrick Zgadzam się, int mainto właściwa rzecz, ale gcci inne kompilatory kompilują to do poprawnie działających programów od lat 90-tych. Całkiem dobrze wyjaśniono tutaj: eskimo.com/~scs/readings/voidmain.960823.html - to po prostu „funkcja”, tak to odbieram .
icyrock.com
1
@ icyrock.com: Wszystko bardzo prawdziwe (w odniesieniu do void main () w C), ale podczas publicznego publikowania prawdopodobnie równie dobrze jest uniknąć debaty, używając funkcji int main (), aby nie odwracać uwagi od głównego punktu.
Clifford
21
Jest w tym ogromna wada. Nie możesz bezpiecznie używać printf w treści obsługi sygnału. Jest to naruszenie bezpieczeństwa sygnału asynchronicznego. Dzieje się tak, ponieważ printf nie jest ponownie wprowadzany. Co się stanie, jeśli program był w trakcie używania printf, gdy naciśnięto Ctrl-C, a twój program obsługi sygnału zacznie go używać w tym samym czasie? Wskazówka: prawdopodobnie się zepsuje. write i fwrite są takie same do użycia w tym kontekście.
Dylan Lukes
2
@ icyrock.com: Wykonywanie czegokolwiek skomplikowanego w obsłudze sygnału spowoduje bóle głowy. Szczególnie w przypadku systemu io.
Martin York
2
@stacker Thanks - myślę, że na koniec warto. Jeśli ktoś natknie się na ten kod w przyszłości, lepiej, aby był jak najbardziej poprawny, niezależnie od tematu pytania.
icyrock.com
30

Dodatek dotyczący platform UN * X.

Zgodnie ze signal(2)stroną podręcznika systemowego dotyczącą systemu GNU / Linux zachowanie signalnie jest tak przenośne, jak zachowanie sigaction:

Zachowanie signal () różni się w różnych wersjach systemu UNIX, a także różniło się w przeszłości w różnych wersjach Linuksa. Unikaj jego używania: zamiast tego użyj sigaction (2).

W Systemie V system nie blokował dostarczania kolejnych wystąpień sygnału, a dostarczenie sygnału zresetowałoby program obsługi do domyślnego. W BSD zmieniła się semantyka.

Następująca odmiana poprzedniej odpowiedzi autorstwa Dirka Eddelbuettela używa sigactionzamiast 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.
źródło
14

Lub możesz ustawić terminal w trybie surowym, na przykład:

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

Teraz powinno być możliwe odczytanie Ctrl+ Cnaciśnięć klawiszy za pomocą fgetc(stdin). Uważaj jednak na używanie tego, ponieważ nie możesz już Ctrl+ Z, Ctrl+ Q, Ctrl+ Sitd. Jak zwykle.

Walter
źródło
11

Ustaw pułapkę (możesz złapać kilka sygnałów jednym przewodnikiem):

sygnał (SIGQUIT, my_handler);
sygnał (SIGINT, my_handler);

Obsługuj sygnał, jak chcesz, ale pamiętaj o ograniczeniach i pułapkach:

void my_handler (int sig)
{
  / * Twój kod tutaj. * /
}
Paul Beckingham
źródło
8

@Peter Varo zaktualizował odpowiedź Dirka, ale Dirk odrzucił zmianę. Oto nowa odpowiedź Petera:

Chociaż powyższy fragment kodu jest poprawny Przykładowo, w miarę możliwości należy korzystać z nowocześniejszych typów i gwarancji oferowanych przez późniejsze standardy. Dlatego tutaj jest bezpieczniejsza i nowoczesna alternatywa dla tych, którzy szukają i zgodna implementacja:

#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 Nagłówek <signal.h>deklaruje typ ... sig_atomic_tbędący (prawdopodobnie zmienną kwalifikowaną) liczbą całkowitą obiektu, do którego można uzyskać dostęp jako niepodzielna jednostka, nawet w obecności przerwań asynchronicznych.

Ponadto:

C11 Standard: 7.14.1.1§5 Jeśli sygnał występuje inaczej niż w wyniku wywołania abortlubraise funkcji , zachowanie jest niezdefiniowane, jeśli program obsługi sygnału odnosi się do dowolnego obiektu z staticczasem przechowywania wątków, który nie jest niepodzielnym obiektem bez blokady inny niż przez przypisanie wartości do obiektu zadeklarowanego jako volatile sig_atomic_t...

MCCCS
źródło
Nie rozumiem (void)_;... Jaki jest jego cel? Czy to dlatego, że kompilator nie ostrzega o nieużywanej zmiennej?
Luis Paulo
1
@LuisPaulo Tak na drugie pytanie.
MCCCS
Właśnie znalazłem tę odpowiedź i miałem zamiar wkleić tutaj ten link! Dzięki :)
Luis Paulo
5

Jeśli chodzi o istniejące odpowiedzi, zwróć uwagę, że obsługa sygnału zależy od platformy. Na przykład Win32 obsługuje znacznie mniej sygnałów niż systemy operacyjne POSIX; zobacz tutaj . Chociaż SIGINT jest zadeklarowany w pliku signal.h na Win32, zobacz uwagę w dokumentacji, która wyjaśnia, że ​​nie zrobi tego, czego można się spodziewać.

Clifford
źródło
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;
}

Funkcja sig_handler sprawdza, czy wartość przekazanego argumentu jest równa SIGINT, a następnie wykonywany jest printf.

Uwielbiam Bisaria
źródło
Nigdy nie wywołuj procedur we / wy w programie obsługi sygnału.
jwdonahue
2

To po prostu wydrukuj przed wyjściem.

#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
źródło
2
Nigdy nie wywołuj I / O w programie obsługi sygnału.
jwdonahue