Kiedyś mi doradzono, że program C ++ powinien ostatecznie wyłapać wszystkie wyjątki. Podane wówczas uzasadnienie było zasadniczo takie, że programy, które zezwalają na wyjątki, pojawiają się poza main()
wejściem w dziwny stan zombie. Powiedziano mi to kilka lat temu i z perspektywy czasu uważam, że zaobserwowane zjawisko było spowodowane długim generowaniem wyjątkowo dużych zrzutów rdzenia z przedmiotowego projektu.
W tym czasie wydawało się to dziwne, ale przekonujące. To całkowicie nonsensowne, że C ++ powinien „ukarać” programistów za to, że nie wyłapali wszystkich wyjątków, ale dowody przed mną zdawały się to potwierdzać. W przypadku omawianego projektu programy, które wprowadziły nieprzechwycone wyjątki, wydawały się wchodzić w dziwny stan zombie - lub, jak podejrzewam, przyczyną była teraz, proces pośród niechcianego zrzutu rdzenia jest niezwykle trudny do zatrzymania.
(Dla każdego, kto zastanawia się, dlaczego nie było to wtedy bardziej oczywiste: projekt wygenerował dużą ilość danych wyjściowych w wielu plikach z wielu procesów, które skutecznie przesłaniały dowolny rodzaj aborted (core dumped)
wiadomości, aw tym konkretnym przypadku pośmiertne badanie zrzutów rdzenia nie było jest to ważna technika debugowania, dlatego nie rzucano dużo na zrzuty rdzenia. Problemy z programem zwykle nie zależały od stanu nagromadzonego na podstawie wielu zdarzeń w czasie przez program długo działający, ale raczej początkowe dane wejściowe do programu krótkotrwałego (< 1 godzina), więc bardziej praktyczne było ponowne uruchomienie programu z tymi samymi danymi wejściowymi z kompilacji debugowania lub w debugerze, aby uzyskać więcej informacji.)
Obecnie nie jestem pewien, czy istnieje jakaś poważna zaleta lub wada wychwytywania wyjątków wyłącznie w celu zapobiegania wyjściu wyjątków main()
.
Niewielką zaletą, jaką mogę wymyślić, aby pozwolić na wyjątki, aby zniknęły w przeszłości, main()
jest to, że powoduje std::exception::what()
to wydrukowanie wyniku na terminalu (przynajmniej w przypadku programów skompilowanych gcc w systemie Linux). Z drugiej strony jest to trywialne do osiągnięcia przez wychwycenie wszystkich wyjątków wynikających z std::exception
i wydrukowanie wyniku, std::exception::what()
a jeśli pożądane jest wydrukowanie wiadomości z wyjątku, który z niej nie pochodzi std::exception
, należy ją złapać przed wyjściem main()
w celu wydrukowania wiadomość.
Skromną wadą, jaką mogę wymyślić, aby pozwolić na wyjątki, aby się wybić, main()
jest to, że mogą powstawać niepożądane zrzuty rdzeni. W przypadku procesu używającego dużej ilości pamięci może to być dość uciążliwe, a kontrolowanie zachowania zrzutu pamięci z programu wymaga wywołań funkcji specyficznych dla systemu operacyjnego. Z drugiej strony, jeśli pożądany jest zrzut pamięci i wyjście, można to w dowolnym momencie osiągnąć przez wywołanie, std::abort()
a wyjście bez zrzutu pamięci można w dowolnym momencie wywołać std::exit()
.
Anegdotycznie nie sądzę, żebym kiedykolwiek widział domyślną what(): ...
wiadomość drukowaną przez szeroko rozpowszechniany program po awarii.
Jakie są, jeśli w ogóle, mocne argumenty przemawiające za lub przeciw dopuszczeniu wyjątków C ++ main()
?
Edycja: Na tej stronie jest wiele ogólnych pytań dotyczących obsługi wyjątków. Moje pytanie dotyczy w szczególności wyjątków w C ++, których nie można obsłużyć i które doprowadziły do końca main()
- być może można wydrukować komunikat o błędzie, ale jest to natychmiastowy błąd zatrzymania.
źródło
Odpowiedzi:
Jednym z problemów z dopuszczeniem wyjątków do głównego jest to, że program zakończy się wywołaniem, do
std::terminate
którego ma zostać wywołane zachowanie domyślnestd::abort
. Jest to implementacja zdefiniowana tylko wtedy, gdy rozwijanie stosu jest wykonywane przed wywołaniem,terminate
aby Twój program mógł zakończyć się bez wywoływania jednego destruktora! Jeśli masz jakieś zasoby, które naprawdę trzeba było przywrócić przez wywołanie destruktora, jesteś w marynacie ...źródło
std::abort
nie, a tym samym nieudane twierdzenia też nie. Warto również zauważyć, że we współczesnych systemach operacyjnych sam system oczyści wiele zasobów (pamięć, uchwyty plików itp.), Które są powiązane z identyfikatorem procesu. Na koniec wspomniałem również o „Defense in Depth”: nie można oczekiwać, że wszystkie inne procesy będą odporne na błędy i zawsze zwrócą zdobyte zasoby (sesje, ładnie skończą się pisanie plików ...), które musisz zaplanować to ...WM_POWERBROADCAST
wiadomości. Działa to tylko wtedy, gdy komputer jest zasilany bateryjnie (jeśli korzystasz z laptopa lub zasilacza UPS).Głównym powodem, dla którego nie można uniknąć wyjątków,
main
jest to, że w przeciwnym razie tracisz możliwość kontrolowania sposobu zgłaszania problemu użytkownikom.W przypadku programu, który nie jest przeznaczony do używania przez długi czas lub do szerokiej dystrybucji, można zaakceptować zgłaszanie nieoczekiwanych błędów niezależnie od sposobu, w jaki system operacyjny zdecyduje się to zrobić (na przykład wyświetlenie okna dialogowego błędu w systemie Windows ).
W przypadku programów, które sprzedajesz lub które są oferowane ogółowi społeczeństwa przez organizację, której reputację należy utrzymywać, ogólnie lepiej jest zgłosić w miły sposób, że napotkałeś nieoczekiwany problem i spróbować zaoszczędzić jak najwięcej dane użytkownika, jak to możliwe. Nietracenie pracy użytkownika przez pół dnia i nieoczekiwane zawieszenie się, ale częściowe zamknięcie jest ogólnie znacznie lepsze dla reputacji firmy niż alternatywa.
źródło
main()
ma wiele wspólnego z systemem operacyjnym? System operacyjny może nic nie wiedzieć o C ++. Domyślam się, że jest to określone przez kod, który kompilator wstawia gdzieś do programu.TL; DR : Co mówi specyfikacja?
Objazd techniczny ...
Gdy zgłoszony zostanie wyjątek i żaden program obsługi nie jest na niego gotowy:
std::terminate
nazywa się, co domyślnie przerywaTo ostatnie może być przydatne w przypadku bardzo rzadkich błędów (ponieważ ich odtwarzanie jest procesem marnującym czas).
To, czy złapać wszystkie wyjątki, czy nie, jest ostatecznie kwestią specyfikacji:
W przypadku każdego programu produkcyjnego należy to określić i postępować zgodnie ze specyfikacją (i być może argumentować, że należy ją zmienić).
W przypadku szybkich zestawionych programów, z których mogą korzystać tylko osoby techniczne (ty, twoi koledzy z drużyny), jedno jest w porządku. Zalecam, aby zawiesił się i skonfigurował środowisko, aby uzyskać raport lub nie, w zależności od potrzeb.
źródło
Wyłapany wyjątek daje możliwość wydrukowania ładnego komunikatu o błędzie lub nawet próby przywrócenia go po błędzie (być może po prostu poprzez ponowne uruchomienie aplikacji).
Jednak w C ++ wyjątek nie przechowuje informacji o stanie programu, gdy został zgłoszony. Jeśli go złapiesz, cały taki stan zostanie zapomniany, natomiast jeśli pozwolisz, aby program się zawiesił, zwykle jest on nadal obecny i można go odczytać ze zrzutu programu, co ułatwia debugowanie.
Więc to jest kompromis.
źródło
W momencie, gdy wiesz, że musisz przerwać, idź
std::terminate
już i zadzwoń, aby ograniczyć dalsze obrażenia.Jeśli wiesz, że możesz bezpiecznie zakończyć, zrób to zamiast tego. Pamiętaj, że odwijanie stosu nie jest gwarantowane, gdy wyjątek nigdy nie zostanie złapany, więc łap i odrzuć.
Jeśli możesz bezpiecznie zgłosić / zarejestrować błąd lepiej niż system zrobi to sam, śmiało.
Ale naprawdę upewnij się, że nie przypadkowo pogorszysz sytuację.
Często jest już za późno, aby zapisać dane po wykryciu nieodwracalnego błędu, choć zależy to od konkretnego błędu.
W każdym razie, jeśli twój program jest napisany z myślą o szybkim odzyskaniu, zabicie go może być najlepszym sposobem na jego zakończenie, nawet jeśli jest to zwykłe zamknięcie.
źródło
Rozbijanie się z wdziękiem jest dobrą rzeczą przez większość czasu - ale są kompromisy. Czasami dobrze jest upaść. Powinienem wspomnieć, że głównie myślę o debugowaniu w bardzo dużych. W przypadku prostego programu - chociaż może być nadal przydatny, nie jest tak pomocny, jak w przypadku bardzo złożonego programu (lub kilku złożonych programów wchodzących w interakcje).
Nie chcesz upaść publicznie (chociaż jest to naprawdę nieuniknione - awarie programów, a o tym, o czym tu mówimy, nie ma mowy o naprawdę matematycznie weryfikowalnym programie, który nie ulega awarii). Pomyśl o Bill Gates BSODing w trakcie demo - źle, prawda? Niemniej jednak możemy spróbować dowiedzieć się, dlaczego rozbiliśmy się i nie rozbiliśmy ponownie w ten sam sposób.
Włączyłem funkcję raportowania błędów systemu Windows, która tworzy lokalne zrzuty awarii w przypadku nieobsługiwanych wyjątków. Działa cuda, jeśli masz pliki symboli powiązane z twoją (zawieszającą się) kompilacją. Co ciekawe, ponieważ skonfigurowałem to narzędzie, chcę więcej awarii - ponieważ uczę się od każdej awarii. Potem mogę naprawić błędy i mniej awarii.
Na razie chcę, aby moje programy rzuciły się na szczyt i uległy awarii. W przyszłości mógłbym z wdziękiem zjadać wszystkie moje wyjątki - ale nie jeśli sprawię, że będą dla mnie działać.
Możesz przeczytać więcej o lokalnych zrzutach awaryjnych tutaj, jeśli jesteś zainteresowany: Zbieranie zrzutów w trybie użytkownika
źródło
Ostatecznie, jeśli wyjątek pojawi się poza main (), spowoduje to awarię aplikacji, a moim zdaniem aplikacja nigdy nie powinna ulec awarii. Jeśli można zawiesić aplikację w jednym miejscu, to dlaczego nigdzie nie? Po co w ogóle zawracać sobie głowę obsługą wyjątków? (Sarkazm, niezbyt sugerujący to ...)
Możesz mieć globalną próbę / catch, która drukuje elegancką wiadomość informującą użytkownika, że wystąpił nieusuwalny błąd i że musi on wysłać e-maila do boba w IT na ten temat lub coś podobnego, ale zawieszanie się jest całkowicie nieprofesjonalne i niedopuszczalne. Jest to trywialne, aby zapobiec, a użytkownik może poinformować użytkownika o tym, co się właśnie wydarzyło i jak to naprawić, aby to się nie powtórzyło.
Crashing to ściśle amatorska godzina.
źródło
main
. Jeśli nie wiesz, jak sobie z tym poradzić, udawanie, że to robisz, jest oczywiście strasznym błędem.