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():
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.
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.
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>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 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:
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.
@Peter Varo zaktualizował odpowiedź Dirka, ale Dirk odrzucił zmianę. Oto nowa odpowiedź Petera:
Chociaż powyższy fragment kodu jest poprawny c89Przykł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ąc99 i c11 zgodna implementacja:
#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 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...
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ć.
#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;}
Funkcja sig_handler sprawdza, czy wartość przekazanego argumentu jest równa SIGINT, a następnie wykonywany jest printf.
Odpowiedzi:
Z obsługą sygnału.
Oto prosty przykład odwracający
bool
używany wmain()
: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.
źródło
bool volatile keepRunning = true;
w 100% bezpieczny. Kompilator może swobodnie buforowaćkeepRunning
w 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ą.sig_atomic_t
alboatomic_bool
wpiszę. 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 :)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:
źródło
int main
to właściwa rzecz, alegcc
i 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 .Dodatek dotyczący platform UN * X.
Zgodnie ze
signal(2)
stroną podręcznika systemowego dotyczącą systemu GNU / Linux zachowaniesignal
nie jest tak przenośne, jak zachowaniesigaction
: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
sigaction
zamiastsignal
:źródło
volatile sig_atomic_t
być dobrze zdefiniowanyLub możesz ustawić terminal w trybie surowym, na przykład:
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.źródło
Ustaw pułapkę (możesz złapać kilka sygnałów jednym przewodnikiem):
Obsługuj sygnał, jak chcesz, ale pamiętaj o ograniczeniach i pułapkach:
źródło
@Peter Varo zaktualizował odpowiedź Dirka, ale Dirk odrzucił zmianę. Oto nowa odpowiedź Petera:
Chociaż powyższy fragment kodu jest poprawny c89Przykł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ąc99 i c11 zgodna implementacja:
Ponadto:
źródło
(void)_;
... Jaki jest jego cel? Czy to dlatego, że kompilator nie ostrzega o nieużywanej zmiennej?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ć.
źródło
Funkcja sig_handler sprawdza, czy wartość przekazanego argumentu jest równa SIGINT, a następnie wykonywany jest printf.
źródło
To po prostu wydrukuj przed wyjściem.
źródło