Pojawia się pytanie o używanie exit
w C ++. W odpowiedzi jest mowa o tym, że nie jest to dobry pomysł głównie z powodu RAII, np. Jeśli exit
zostanie wywołany gdzieś w kodzie to destruktory obiektów nie zostaną wywołane, stąd jeśli np. Destruktor miał zapisywać dane do pliku, to się nie stanie , ponieważ destruktor nie został wywołany.
Interesowało mnie, jak wygląda ta sytuacja w C. Czy podobne kwestie dotyczą również C? Pomyślałem, że skoro w C nie używamy konstruktorów / destruktorów, sytuacja może wyglądać inaczej w C. Czy można używać exit
w C?
Widziałem funkcje, takie jak poniżej, które uważam za dobre w niektórych przypadkach, ale byłem zainteresowany, czy mamy podobne problemy w C z używaniem exit
, jak opisano powyżej w C ++? (co spowodowałoby, że używanie funkcji takich jak poniżej nie byłoby dobrym pomysłem).
void die(const char *message)
{
if(errno) {
perror(message);
} else {
printf("ERROR: %s\n", message);
}
exit(1);
}
SIGTERM
sygnału w POSIX i Linuxie… Dobrze zachowujące się serwery powinny sobie z tym dobrze radzić. I powinieneś unikać używaniaSIGKILL
(tj. Spróbujkill -TERM
wtedykill -QUIT
i dopiero późniejkill -KILL
jako administrator)exit()
funkcji nie jest istotne (a wysoka liczba głosów jest zaskakująca - nie jest to dobre pytanie). Dlaczego nie powinienem używać funkcji exit () w C? byłby dobrym kandydatem, gdyby miał odpowiedzi na to pytanie. Nie ma; odwrotne zamknięcie tego jako duplikatu jest właściwe - i zrobiłem to.Odpowiedzi:
Zamiast
abort()
tegoexit()
funkcja w C jest uważana za „pełne wdzięku” wyjście.Z C11 (N1570) 7.22.4.4/p2 Funkcja wyjścia (moje wyróżnienie ):
Norma mówi również w 7.22.4.4/p4, że:
Warto też patrząc na 7.21.3 / P5 Files :
Jednak, jak wspomniano w komentarzach poniżej, nie można zakładać, że obejmie on każdy inny zasób , więc może być konieczne
atexit()
indywidualne odwołanie się do wywołań zwrotnych w celu ich wydania. W rzeczywistości jest to dokładnie to, coatexit()
jest przeznaczone do zrobienia, jak jest to napisane w 7.22.4.2/p2 Funkcja atexit :Warto zauważyć, że standard C nie mówi dokładnie, co powinno się stać z obiektami o przydzielonym czasie przechowywania (tj.
malloc()
), Co wymaga, abyś był świadomy tego, jak to się robi w konkretnej implementacji. W przypadku nowoczesnych systemów operacyjnych zorientowanych na hosta jest prawdopodobne, że system zajmie się tym problemem, ale nadal możesz chcieć zająć się tym samodzielnie, aby wyciszyć debuggery pamięci, takie jak Valgrind .źródło
stdio
, czy nie , czyli jest równoważne aclose()
na FD. Nie wiem, w jakim sensie @BlueMoon uważa, że to nieprawda, ale nie mniej błędne niż wezwanieclose()
, a jeśli wymagane są dalsze wyjaśnienia, to właśnie po toatexit()
.atexit()
można użyć. Używanieexit()
samego siebie nie jest bardziej brutalne niż robieniereturn
zmain()
.atexit
przeciwnym razie jest w porządku. @BlueMoon: Nie sądzę, aby używanie takiej funkcji jakdie
oznaczało, że nie można programować, w kilku przypadkach można chcieć jej użyć. W przeciwnym razie możesz sobie z tym poradzić, używając po prostu zwracanych wartości itp.Tak, używanie
exit
w C.Aby zapewnić wszystkie bufory i bezpieczne, uporządkowane zamykanie, zaleca się użycie tej funkcji
atexit
, więcej informacji na ten temat tutajPrzykładowy kod wyglądałby tak:
void cleanup(void){ /* example of closing file pointer and free up memory */ if (fp) fclose(fp); if (ptr) free(ptr); } int main(int argc, char **argv){ /* ... */ atexit(cleanup); /* ... */ return 0; }
Teraz, za każdym razem, gdy
exit
zostanie wywołana, funkcjacleanup
zostanie wykonana, co może pomieścić wdzięczne zamknięcie, wyczyszczenie buforów, pamięci itp.źródło
Nie masz konstruktorów i destruktorów, ale możesz mieć zasoby (np. Pliki, strumienie, gniazda) i ważne jest, aby je poprawnie zamknąć. Bufor nie mógł zostać zapisany synchronicznie, więc wyjście z programu bez wcześniejszego prawidłowego zamknięcia zasobu może doprowadzić do uszkodzenia.
źródło
exit()
(zamiast_exit()
),atexit
procedury są wywoływane, astdio
buforowanie jest usuwane na dysk.exit()
jest właśnie po to, aby umożliwić uporządkowane wyjście z programu.atexit
mogą pomóc, choć nie zawsze są odpowiednie.exit
, jeśli chcesz, ale jest to globalny skok sterowania, który jest dostarczany ze wszystkimi downsides o nieuporządkowanej kontroli, które omówiliśmy. Jako sposób na zakończenie stanu awarii może to być właściwe (i wydaje się, że jest to, czego chce OP), chociaż dla normalnego przepływu sterowania prawdopodobnie wolałbym zaprojektować główną pętlę z odpowiednim warunkiem wyjścia, aby rzeczywiście skończyć wracając z main.Używanie
exit()
jest OKDwa główne aspekty projektowania kodu, o których jeszcze nie wspomniano, to „wątkowanie” i „biblioteki”.
W programie jednowątkowym, w kodzie, który piszesz w celu zaimplementowania tego programu, użycie
exit()
jest w porządku. Moje programy używają go rutynowo, gdy coś poszło nie tak i kod nie zostanie odzyskany.Ale…
Jednak wezwanie
exit()
jest działaniem jednostronnym, którego nie można cofnąć. Dlatego zarówno „wątkowanie”, jak i „biblioteki” wymagają dokładnego przemyślenia.Programy gwintowane
Jeśli program jest wielowątkowy, użycie
exit()
jest dramatyczną akcją, która kończy wszystkie wątki. Wyjście z całego programu będzie prawdopodobnie niewłaściwe. Może być właściwe wyjście z wątku i zgłoszenie błędu. Jeśli znasz projekt programu, być może jednostronne wyjście jest dopuszczalne, ale generalnie nie będzie do zaakceptowania.Kod biblioteki
I ta klauzula „świadomy projektu programu” odnosi się również do kodu w bibliotekach. Bardzo rzadko jest poprawne wywołanie funkcji biblioteki ogólnego przeznaczenia
exit()
. Słusznie byłbyś zdenerwowany, gdyby jedna ze standardowych funkcji biblioteki C nie zwróciła się tylko z powodu błędu. (Oczywiście, jak funkcjonujeexit()
,_Exit()
,quick_exit()
,abort()
nie są przeznaczone do zwrotu, to co innego.) Funkcje w bibliotece C dlatego też „nie może upaść” lub powrót wskazanie błędu jakoś. Jeśli piszesz kod, który ma trafić do biblioteki ogólnego przeznaczenia, musisz dokładnie rozważyć strategię obsługi błędów w swoim kodzie. Powinien pasować do strategii obsługi błędów programów, z którymi ma być używany, lub można skonfigurować obsługę błędów.Mam szereg funkcji bibliotecznych (w pakiecie z nagłówkiem
"stderr.h"
, nazwą, która krąży po cienkim lodzie), które są przeznaczone do zakończenia, ponieważ są używane do raportowania błędów. Te funkcje kończą się zgodnie z projektem. W tym samym pakiecie znajduje się szereg funkcji, które zgłaszają błędy i nie zamykają się. Wychodzące funkcje są oczywiście implementowane w kategoriach funkcji nie wychodzących, ale jest to wewnętrzny szczegół implementacji.Mam wiele innych funkcji bibliotecznych i wiele z nich polega na
"stderr.h"
kodzie do raportowania błędów. To decyzja projektowa, którą podjąłem i z którą się zgadzam. Ale kiedy błędy są zgłaszane za pomocą funkcji, które kończą pracę, ogranicza to ogólną użyteczność kodu biblioteki. Jeśli kod wywołuje funkcje raportowania błędów, które nie kończą się, to główne ścieżki kodu w funkcji muszą poradzić sobie z błędami, zwracając je normalnie - wykryj je i przekaż wskazanie błędu do kodu wywołującego.Kod mojego pakietu raportowania błędów jest dostępny w moim repozytorium SOQ (Stack Overflow Pytania) na GitHub jako pliki
stderr.c
orazstderr.h
w podkatalogu src / libsoq .źródło
abort()
kiedy trzeba przydzielić pamięć przez jednąmalloc()
lubrealloc()
, Wyobraź sobie, że masz aplikację, która łączy się ze 100 bibliotekami i zastanawiasz się, która z nich i jak spowodowała awarię aplikacji. Co więcej, nie znalazłem żadnej wzmianki o tymabort()
w ich dokumentacji (ale nie zrozumcie mnie źle. To świetna biblioteka do tego celu).stderr
jest zwykle buforowane liniowo. Jeśli wyjście kończy się znakiem nowej linii, i tak zostanie opróżnione przez system.Jednym z powodów, których należy unikać
exit
w funkcjach innych niżmain()
możliwość wyrwania kodu z kontekstu. Pamiętaj, że wyjście jest rodzajem nielokalnego przepływu sterowania . Jak nieuchwytne wyjątki.Na przykład możesz napisać niektóre funkcje zarządzania pamięcią masową, które kończą się w przypadku krytycznego błędu dysku. Wtedy ktoś decyduje się przenieść je do biblioteki. Wyjście z biblioteki to coś, co spowoduje, że program wywołujący zakończy pracę w stanie niespójnym, na który może nie być przygotowany.
Lub możesz uruchomić go w systemie wbudowanym. Nie ma nigdzie wyjść na cały tras rzeczą w
while(1)
pętlimain()
. Może nawet nie być zdefiniowany w bibliotece standardowej.źródło
W zależności od tego, co robisz, wyjście może być najbardziej logicznym sposobem wyjścia z programu w C. Wiem, że jest to bardzo przydatne do sprawdzania, czy łańcuchy wywołań zwrotnych działają poprawnie. Weźmy przykład callback, którego ostatnio używałem:
unsigned char cbShowDataThenExit( unsigned char *data, unsigned short dataSz,unsigned char status) { printf("cbShowDataThenExit with status %X (dataSz %d)\n", status, dataSz); printf("status:%d\n",status); printArray(data,dataSz); cleanUp(); exit(0); }
W głównej pętli ustawiam wszystko dla tego systemu, a następnie czekam chwilę (1) pętla. Możliwe jest utworzenie flagi globalnej, aby zamiast tego wyjść z pętli while, ale jest to proste i robi to, co trzeba. Jeśli masz do czynienia z otwartymi buforami, takimi jak pliki i urządzenia, powinieneś je wyczyścić przed zamknięciem, aby zachować spójność.
źródło
To straszne w dużym projekcie, kiedy każdy kod może wyjść poza coredump. Śledzenie jest bardzo ważne w utrzymaniu serwera online.
źródło