8.12 — Zatrzymanie (wcześniejsze wyjście z programu)

Ostatnią kategorią instrukcji kontroli przepływu, którą omówimy w tym rozdziale, jest zatrzymanie. A halt jest instrukcją kontroli przepływu, która kończy program. W C++ zatrzymania są implementowane jako funkcje (a nie słowa kluczowe), więc nasze instrukcje halt będą wywołaniami funkcji.

Zróbmy krótką wycieczkę i podsumujmy, co dzieje się, gdy program kończy się normalnie. Kiedy main() funkcja powraca (albo przez dotarcie do końca funkcji, albo przez return statement), dzieje się wiele różnych rzeczy.

Po pierwsze, ponieważ opuszczamy funkcję, wszystkie zmienne lokalne i parametry funkcji ulegają zniszczeniu (jak zwykle).

Następnie wywoływana jest specjalna funkcja o nazwie std::exit() , ze zwracaną wartością from main() (the status code) przekazane jako argument. Czym więc jest std::exit()?

Funkcja std::exit()

std::exit() to funkcja, która powoduje normalne zakończenie programu. Normalne zakończenie oznacza, że ​​program zakończył działanie w oczekiwany sposób. Należy zauważyć, że termin normal termination nie sugeruje niczego na temat tego, czy program odniósł sukces (po to właśnie jest status code ). Załóżmy na przykład, że piszesz program, w którym oczekiwałeś, że użytkownik wpisze nazwę pliku do przetworzenia. Jeśli użytkownik wpisał nieprawidłową nazwę pliku, program prawdopodobnie zwróci wartość różną od zera status code w celu wskazania stanu awarii, ale nadal będzie normal termination.

std::exit() wykonywał szereg funkcji czyszczących. Najpierw niszczone są obiekty o statycznym czasie przechowywania. Następnie przeprowadzane jest inne czyszczenie plików, jeśli jakieś pliki były używane. Na koniec kontrola jest zwracana z powrotem do systemu operacyjnego, z argumentem przekazywanym do std::exit() używanym jako status code.

Wywołanie std::exit() jawnie

Chociaż std::exit() jest wywoływane niejawnie po powrocie funkcji main() , std::exit() można również wywołać jawnie, aby zatrzymać program przed jego normalnym zakończeniem. Kiedy std::exit() jest wywoływane w ten sposób, będziesz musiał uwzględnić cstdlib .

Kluczowa informacja

std::exit jest wywoływany niejawnie, gdy main() powraca.

Oto przykład użycia std::exit() jawnie:

#include <cstdlib> // for std::exit()
#include <iostream>

void cleanup()
{
    // code here to do any kind of cleanup required
    std::cout << "cleanup!\n";
}

int main()
{
    std::cout << 1 << '\n';
    cleanup();

    std::exit(0); // terminate and return status code 0 to operating system

    // The following statements never execute
    std::cout << 2 << '\n';

    return 0;
}

Ten program wypisuje:

1
cleanup!

Zauważ, że instrukcje po wywołaniu std::exit() nigdy nie są wykonywane, ponieważ program już zakończony.

Chociaż w powyższym programie wywołujemy std::exit() z funkcji main(), std::exit() można ją wywołać z dowolnej funkcji, aby zakończyć program w tym momencie.

std::exit() nie czyści zmiennych lokalnych

Ważna uwaga dotycząca wywoływania std::exit() jawnie: std::exit() nie czyści żadnych zmiennych lokalnych (albo w bieżącej funkcji, albo w funkcjach na stosie wywołań). Oznacza to, że wywołanie std::exit() może być niebezpieczne, jeśli Twój program polega na czyszczeniu jakichkolwiek zmiennych lokalnych.

Ostrzeżenie

Klasa std::exit() funkcja nie czyści zmiennych lokalnych w bieżącej funkcji ani nie czyści stosu wywołań.

std::atexit

Ponieważ std::exit() zakończa program natychmiast, możesz chcieć ręcznie przeprowadzić czyszczenie przed zakończeniem. W tym kontekście czyszczenie oznacza zamykanie bazy danych lub połączeń sieciowych, zwalnianie przydzielonej pamięci, zapisywanie informacji w pliku dziennika itp.

Na marginesie…

Gdy aplikacja kończy działanie, nowoczesne systemy operacyjne zazwyczaj czyściją całą pamięć, której aplikacja nie oczyści sama prawidłowo. Prowadzi to do pytania: „po co więc zawracać sobie głowę sprzątaniem po wyjściu?”. Są (co najmniej) dwa powody:

  1. Czyszczenie przydzielonej pamięci to „dobry nawyk”, którego będziesz musiał używać, aby uniknąć wycieków pamięci podczas działania aplikacji. Czyszczenie w niektórych przypadkach jest niespójne i może prowadzić do błędów. Niewłaściwe czyszczenie pamięci może również mieć wpływ na zachowanie niektórych narzędzi, takich jak profilery pamięci (mogą one nie być w stanie odróżnić pamięci, której nieumyślnie nie czyścisz, od pamięci, której celowo nie czyścisz, bo nie musisz).
  2. Istnieją inne rodzaje czyszczenia, które mogą być konieczne, aby program działał w przewidywalny sposób. Na przykład, jeśli zapiszesz dane do pliku, a następnie nieoczekiwanie zakończysz działanie, dane te mogły nie zostać jeszcze przeniesione do pliku i mogą zostać utracone po zamknięciu programu. Zamknięcie pliku przed zamknięciem zapewnia, że ​​wszystkie dane w pamięci podręcznej zostaną zapisane jako pierwsze. Możesz też wysłać dane o sesji użytkownika lub o tym, dlaczego program zamyka się na serwerze, zanim faktycznie nastąpi zamknięcie.

W powyższym przykładzie wywołaliśmy funkcję cleanup() , aby obsłużyć nasze zadania porządkowe. Jednak pamiętanie o ręcznym wywołaniu funkcji czyszczącej przed wywołaniem każdego wywołania metody exit() stanowi obciążenie dla programisty i jest przepisem na błędy.

Aby to ułatwić, C++ oferuje funkcję std::atexit() , która pozwala określić funkcję, która będzie automatycznie wywoływana po zakończeniu programu poprzez std::exit().

Powiązana treść

Przekazywanie funkcji jako argumentów omawiamy na lekcji 20.1 -- Wskaźniki funkcji.

Oto przykład:

#include <cstdlib> // for std::exit()
#include <iostream>

void cleanup()
{
    // code here to do any kind of cleanup required
    std::cout << "cleanup!\n";
}

int main()
{
    // register cleanup() to be called automatically when std::exit() is called
    std::atexit(cleanup); // note: we use cleanup rather than cleanup() since we're not making a function call to cleanup() right now

    std::cout << 1 << '\n';

    std::exit(0); // terminate and return status code 0 to operating system

    // The following statements never execute
    std::cout << 2 << '\n';

    return 0;
}

Ten program ma takie same dane wyjściowe jak poprzedni przykład:

1
cleanup!

Zauważ, że przekazując cleanup() funkcję jako argument, używamy cleanup (nazwy funkcji), a nie cleanup() (co faktycznie wywołałoby tę funkcję).

Zaletą std::atexit() jest to, że musimy ją wywołać tylko raz (prawdopodobnie z poziomu funkcji main()). Ponieważ std::atexit() zostanie automatycznie wywołany przy wyjściu, nie musimy pamiętać o wywołaniu czegokolwiek przed wywołaniem std::exit().

Kilka uwag na temat std::atexit() i funkcji czyszczącej: Po pierwsze, ponieważ std::exit() jest wywoływany niejawnie, gdy main() zakończy się, wywoła to wszelkie funkcje zarejestrowane przez std::atexit() jeśli program zakończy działanie w ten sposób. Po drugie, rejestrowana funkcja nie może przyjmować żadnych parametrów ani zwracać wartości. Na koniec, możesz zarejestrować wiele funkcji czyszczących za pomocą std::atexit() jeśli chcesz, a będą one wywoływane w odwrotnej kolejności rejestracji (ostatnia zarejestrowana funkcja zostanie wywołana jako pierwsza).

Dla zaawansowanych czytelników

W programach wielowątkowych wywołanie std::exit() może spowodować awarię programu (ponieważ wywołanie wątku std::exit() oczyści obiekty statyczne, do których mogą nadal mieć dostęp inne wątki). Z tego powodu w C++ wprowadzono kolejną parę funkcji, które działają podobnie do std::exit() i std::atexit() zwany std::quick_exit() i std::at_quick_exit(). std::quick_exit() zakończenia programu normalnie, ale nie czyszczenia obiektów statycznych i mogą, ale nie muszą, wykonywać inne rodzaje czyszczenia. std::at_quick_exit() pełni tę samą rolę, co std::atexit() dla programów zakończonych std::quick_exit().

std::abort i std::terminate

C++ zawiera dwie inne funkcje związane z zatrzymaniem.

Klasa std::abort() funkcja powoduje nieprawidłowe zakończenie programu. Nieprawidłowe zakończenie oznacza, że w programie wystąpił jakiś nietypowy błąd w czasie wykonywania i program nie mógł kontynuować działania. Na przykład próba podzielenia przez 0 spowoduje nieprawidłowe zakończenie. std::abort() nie powoduje żadnego czyszczenia.

#include <cstdlib> // for std::abort()
#include <iostream>

int main()
{
    std::cout << 1 << '\n';
    std::abort();

    // The following statements never execute
    std::cout << 2 << '\n';

    return 0;
}

Przypadki zobaczymy w przyszłej lekcji. 9.6 — Assert i static_assert gdzie std::abort jest wywoływana niejawnie.

Klasa std::terminate() funkcja jest zwykle używana w połączeniu z exceptions (wyjątki omówimy w późniejszym rozdziale). Chociaż funkcję std::terminate można wywołać jawnie, częściej jest ona wywoływana niejawnie, gdy wyjątek nie jest obsługiwany (oraz w kilku innych przypadkach związanych z wyjątkami). Domyślnie std::terminate() wywołania std::abort().

Kiedy należy zastosować stop?

Krótka odpowiedź brzmi „prawie nigdy”. Niszczenie obiektów lokalnych jest ważną częścią C++ (szczególnie gdy wchodzimy do klas), a żadna z wyżej wymienionych funkcji nie czyści zmiennych lokalnych. Wyjątki są lepszym i bezpieczniejszym mechanizmem obsługi przypadków błędów.

Najlepsza praktyka

Zatrzymania należy używać tylko wtedy, gdy nie ma bezpiecznego lub rozsądnego sposobu na normalny powrót z funkcji głównej. Jeśli nie wyłączyłeś wyjątków, wolisz używać wyjątków do bezpiecznej obsługi błędów.

Wskazówka

Chociaż jawne użycie zatrzymań powinno być minimalizowane, istnieje wiele innych sposobów nieoczekiwanego zamknięcia programu. Na przykład:

  • Aplikacja może ulec awarii z powodu błędu (w takim przypadku system operacyjny ją wyłączy).
  • Użytkownik może zakończyć aplikację na różne sposoby.
  • Użytkownik może wyłączyć (lub stracić) zasilanie swojego komputera.
  • Słońce może przekształcić się w supernową i pochłonąć Ziemię w gigantycznej kuli ognia.

Dobrze zaprojektowany program powinien wytrzymać wyłączenie w dowolnym momencie z minimalnymi konsekwencjami.

Typowym przykładem są współczesne gry, które często okresowo automatycznie zapisują stan gry i ustawienia użytkownika, więc jeśli gra zostanie nieoczekiwanie zamknięta bez zapisania, użytkownik będzie mógł kontynuować później (używając wcześniejszego automatycznego zapisu) bez znacznej utraty postępu.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
148 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze