przerwać, zakończyć czy wyjść?

112

Jaka jest różnica między tymi trzema i jak mam zakończyć program w przypadku wyjątku, którego nie mogę poprawnie obsłużyć?

Nic nie możemy zrobić
źródło
3
To nie jest duplikat, ale raczej podzbiór z kilkoma dobrymi odpowiedziami stackoverflow.com/questions/397075/ ... i również został oznaczony jako C ++!
Ellie Kesselman
std::abortjest rozsądne, jeśli wyjątek nie może zostać rozwiązany w destruktorze.
Daniel
1
Więcej informacji na temat std::terminatetych artykułów można znaleźć w znakomitym blogu Andrzeja poświęconym C ++: akrzemi1.wordpress.com/2011/09/28/who-calls-stdterminate , akrzemi1.wordpress.com/2011/10/05/using-stdterminate
Ohad Schneider

Odpowiedzi:

3

Radziłbym nie używać żadnego z nich. Zamiast catchtego wyjątki, z którymi nie możesz sobie poradzić main()i po prostu returnstamtąd. Oznacza to, że masz gwarancję, że rozwijanie stosu przebiega prawidłowo i wszystkie destruktory są wywoływane. Innymi słowy:

int main() {
    try {
       // your stuff
    }
    catch( ... ) {
       return 1;    // or whatever
    }
}
SS Anne
źródło
8
@Neil: Zgoda w zasadzie, ale wyjątki, z którymi program nie może sobie poradzić, powinny być zgłaszane i ponownie zgłaszane. Pozwól aplikacji się zawiesić.
John Dibling
13
Aby upewnić się, że stos się rozwija, zawsze powinieneś łapać w main. Ale rzuciłbym ponownie z połowu. Ponieważ niektóre systemy operacyjne mają możliwość automatycznego wywoływania infrastruktury debugowania, jeśli została skompilowana w trybie debugowania.
Martin York
5
Wyjątki, które nie są wychwycone nawet przez program obsługi najwyższego poziomu, mogą wywołać funkcję raportowania systemu, która zrzuca proces i przesyła raport wyjątków dla uwagi programistów, taki jak raportowanie błędów systemu Windows, raporty błędów systemu Mac OS X i dzienniki błędów aplikacji na iPhone'a.
JBRWilkinson
6
@John Powodem, dla którego jest to dla mnie zaskakujące, jest to, że chociaż ma to sens w świetle tego, w jaki sposób wyjątki są faktycznie implementowane, łamie abstrakcję, że wyjątek „propaguje w górę stosu”, dopóki nie zostanie znaleziony odpowiedni program obsługi (lub zakończ nazywa). A nieszczelne abstrakcje, choć często nieuniknione, są z konieczności zaskakujące, gdy się je napotka.
Tyler McHenry
11
-1, ponieważ to nie odpowiada na połowę pytania. „Jaka jest różnica między [abort, terminate or exit?]”. To jest lepsza odpowiedź: stackoverflow.com/a/397081/353094 Również stackoverflow.com/a/2820407/353094 to świetna odpowiedź.
leetNightshade
149
  • abort wskazuje na „nienormalny” koniec programu i podnosi sygnał POSIX SIGABRT, co oznacza, że ​​zostanie wywołany każdy program obsługi, który zarejestrowałeś dla tego sygnału, chociaż w każdym przypadku program i tak zakończy działanie posłów. Zwykle używałbyś abortw programie C, aby wyjść z nieoczekiwanego przypadku błędu, w którym błąd prawdopodobnie jest błędem w programie, a nie czymś w rodzaju złego wejścia lub awarii sieci. Na przykład, możesz, abortjeśli okaże się, że struktura danych ma wskaźnik NULL, a to logicznie nigdy nie powinno się zdarzyć.

  • exit oznacza „normalne” zakończenie programu, chociaż może to nadal oznaczać błąd (ale nie błąd). Innymi słowy, możesz exitz kodem błędu, jeśli użytkownik podał dane wejściowe, których nie można przeanalizować lub nie można odczytać pliku. Kod zakończenia równy 0 oznacza sukces. exitopcjonalnie także wywołuje programy obsługi przed zakończeniem programu. Są one zarejestrowane z funkcjami atexiti on_exit.

  • std :: terminate jest tym, co jest automatycznie wywoływane w programie C ++, gdy występuje nieobsługiwany wyjątek. Jest to zasadniczo odpowiednik C ++ abort, przy założeniu, że zgłaszasz wszystkie wyjątkowe błędy za pomocą wyrzucania wyjątków. To wywołuje procedurę obsługi ustawioną przez std::set_terminatefunkcję, która domyślnie po prostu wywołuje abort.

W C ++ zazwyczaj chcesz uniknąć wywołania abortlub exitbłędu, ponieważ lepiej jest wyrzucić wyjątek i pozwolić kodowi na wyższym poziomie stosu wywołań zdecydować, czy zakończenie programu jest właściwe. To, czy użyjesz exitdo sukcesu, jest kwestią okoliczności - czy sensowne jest zakończenie programu w innym miejscu niż instrukcja return w main.

std::terminatenależy traktować jako narzędzie do raportowania błędów ostatniej szansy, nawet w C ++. Problem std::terminatepolega na tym, że program obsługi terminacji nie ma dostępu do wyjątku, który nie został obsłużony, więc nie ma sposobu, aby stwierdzić, co to było. Zwykle znacznie lepiej jest zawinąć całość main w try { } catch (std::exception& ex) { }bloku. Przynajmniej wtedy możesz zgłosić więcej informacji o wyjątkach pochodzących z std::exception(chociaż oczywiście wyjątki, które nie pochodzą z std::exception, nadal byłyby nieobsłużone).

Zawijanie treści mainin try { } catch(...) { }nie jest dużo lepsze niż ustawienie procedury obsługi zakończenia, ponieważ znowu nie masz dostępu do danego wyjątku. Edycja: odpowiedź według Neila Butterwortha, jest korzyść w tym, że w tym przypadku stos jest rozwijany, co (nieco zaskakujące) nie jest prawdziwe w przypadku nieobsługiwanego wyjątku.

Tyler McHenry
źródło
10
Czy możesz zaktualizować tę odpowiedź za pomocą informacji C ++ 11? Wygląda na to, że są teraz sposoby na uzyskanie wyjątku w przechwytywaniu (...) i w module obsługi terminacji.
Klaim
1
W C ++ zakończyć program obsługi nie mają dostępu do wyjątku przez std::current_exception(). Zobacz przykład tutaj: akrzemi1.wordpress.com/2011/10/05/using-stdterminate
anorm
Nie ma znaczenia, że ​​możesz uzyskać bieżący wyjątek, ponieważ nie możesz go sprawdzić. Wszystko, co możesz zrobić, to rzucić go ponownie.
seattlecpp
2
@seattlecpp, możesz go ponownie wrzucić i złapać odniesienie do niego, które możesz następnie sprawdzić
gpeche
16

std :: abort i std :: exit (i więcej: std :: _ Exit, std :: quick_exit) to tylko funkcje niższego poziomu. Używasz ich, aby powiedzieć programowi, co dokładnie chcesz, aby zrobił: jakie destruktory (i czy) ma wywołać, jakie inne funkcje czyszczące mają wywołać, jaką wartość zwrócić itp.

std :: terminate jest abstrakcją wyższego poziomu: jest wywoływana (przez środowisko wykonawcze lub przez ciebie) w celu wskazania, że ​​wystąpił błąd w programie i że z jakiegoś powodu nie można go obsłużyć przez zgłoszenie wyjątku. Konieczność tego zwykle pojawia się, gdy błąd pojawia się w samym mechanizmie wyjątków, ale możesz go użyć w dowolnym momencie, gdy nie chcesz, aby program kontynuował pracę poza podanym błędem. Skompilowałem pełną listę sytuacji, w których std :: terminate jest wywoływane w moim poście. Nie jest określone, co robi std :: terminate, ponieważ masz nad nim kontrolę. Możesz skonfigurować zachowanie, rejestrując dowolne funkcje. Ograniczenia, które masz, są takie, że funkcja nie może wrócić do miejsca błędu i nie może wyjść przez wyjątek, ale technicznie możesz nawet uruchomić pompę wiadomości w środku. Aby zobaczyć listę przydatnych rzeczy, które możesz zrobić w środku, zobacz mój drugi wpis .

W szczególności zwróć uwagę, że std :: terminate jest uważane za procedurę obsługi wyjątków w kontekstach, w których std :: terminate jest wywoływana z powodu wyrzuconego wyjątku, którego nie można obsłużyć, i możesz sprawdzić, jaki był wyjątek i sprawdzić go za pomocą C ++ 11 przy użyciu std :: rethrow_exception i std :: current_exception. To wszystko jest w moim poście .

Andrzeja
źródło
Czy warto mieć program obsługi czyszczenia na wypadek, gdyby program zakończył działanie z powodu sygnałów systemowych? Na przykład nieprawidłowy dostęp do pamięci prowadzi do wygenerowania sygnału SIGSEGV. W takim przypadku, czy dobrze jest po prostu pozwolić programowi na zakończenie pracy i mieć plik core lub zarejestrować procedurę obsługi sygnału, aby wykonać czyszczenie? Czy są jakieś obawy związane z czyszczeniem podczas obsługi sygnałów systemowych w porównaniu do czyszczenia podczas obsługi std :: terminate?
kartik trivikram
12

quick_exit () !

Jeśli twój program jest wielowątkowy, wywołanie exit()najprawdopodobniej spowoduje awarię, ponieważ globalne / statyczne std::threadobiekty będą próbowały zniszczyć bez opuszczania ich wątków.

Jeśli chcesz zwrócić kod błędu i wyjść z programu (mniej więcej) normalnie, wywołaj quick_exit()programy wielowątkowe. W przypadku nieprawidłowego zakończenia (bez możliwości określenia kodu błędu) abort()lub std::terminate()można zadzwonić.

Uwaga: funkcja quick_exit () nie była obsługiwana przez MSVC ++ do wersji 2015.

Serge Rogatch
źródło
4
  • terminate pozostawia możliwość zarejestrowania, co się stanie, gdy zostanie wywołana. Powinien być jednym z dwóch pozostałych.
  • exit to normalne wyjście umożliwiające określenie statusu wyjścia. Programy obsługi zarejestrowane przez at_exit () są uruchamiane
  • abort jest nieprawidłowym wyjściem. Jedyne, co jest uruchamiane, to program obsługi sygnału dla SIGABRT.
AProgrammer
źródło
4
  • terminate () jest wywoływana automatycznie, gdy wystąpi wyjątek, którego nie można obsłużyć. Domyślnie terminate () wywołuje abort (). Możesz ustawić niestandardowy uchwyt za pomocą funkcji set_terminate ().

    abort () wysyła sygnał SIGABRT.

    exit () niekoniecznie jest złą rzeczą. Pomyślnie wychodzi z aplikacji i wywołuje funkcje atexit () w kolejności LIFO. Zwykle nie widzę tego w aplikacjach C ++, jednak widzę to w wielu aplikacjach opartych na Uniksie, w których na końcu wysyła kod zakończenia. Zwykle wyjście (0) oznacza pomyślne uruchomienie aplikacji.

Daniel
źródło
8
Nieudany! Zarówno w systemie Unix, jak i DOS, exit (0) oznacza sukces, a każda inna wartość przekazana do exit () wskazuje na niepowodzenie, a nie na odwrót!
Richard Barrell