instrukcja return vs exit () w main ()

197

Czy powinienem używać exit()czy tylko returnoświadczeń w main()? Osobiście faworyzuję returninstrukcje, ponieważ uważam, że to jak czytanie dowolnej innej funkcji, a kontrola przepływu podczas czytania kodu jest płynna (moim zdaniem). I nawet jeśli chcę zmienić tę main()funkcję, returnwybór wydaje się lepszym wyborem niż exit().

Czy exit()robi coś specjalnego, returnco nie?

Srikanth
źródło

Odpowiedzi:

277

W rzeczywistości jest różnica, ale jest subtelna. Ma więcej implikacji dla C ++, ale różnice są ważne.

Kiedy zadzwonić returnw main()destruktory zostanie wywołana dla moich lokalnie scoped obiektów. Jeśli zadzwonię exit(), żaden obiekt destruktor nie zostanie wywołany dla moich obiektów o lokalnym zasięgu! Przeczytaj to ponownie. exit() nie wraca . Oznacza to, że gdy go nazywam, nie ma „żadnych backsies”. Wszelkie obiekty utworzone w tej funkcji nie zostaną zniszczone. Często nie ma to żadnych implikacji, ale czasem tak, jak zamykanie plików (na pewno chcesz, aby wszystkie twoje dane zostały zrzucone na dysk?).

Pamiętaj, że staticobiekty zostaną wyczyszczone, nawet jeśli zadzwonisz exit(). Na koniec zauważ, że jeśli użyjesz abort(), żadne przedmioty nie zostaną zniszczone. Oznacza to, że nie ma obiektów globalnych, żadnych obiektów statycznych i żadnych obiektów lokalnych.

Postępuj ostrożnie, preferując wyjście zamiast powrotu.

http://groups.google.com/group/gnu.gcc.help/msg/8348c50030cfd15a

Wolna pamięć
źródło
1
abort () kończy działanie z warunkiem błędu (niezerowy kod wyjścia) i może być nawet rdzeniem. Jeśli chcesz wyjść bez wywoływania statycznych destruktorów, użyj _exit.
7
@ Mike: istnieje różnica między buforami plików biblioteki C a obiektami strumienia plików C ++. exit () - będący częścią biblioteki C - został zaprojektowany do koordynowania i opróżniania tego pierwszego, ale może ominąć ten drugi: nawet standardowa zawartość fstream C ++ nie jest opróżniana na dysk (spróbuj - zrobiłem, to nie działa w / Linux / GCC), i oczywiście typy zdefiniowane przez użytkownika, które buforowały operacje we / wy, nie można oczekiwać, że będą również opróżniane.
Tony Delroy
9
Uwaga: Stwierdzenie: żaden obiekt destruktor nie zostanie wywołany dla moich obiektów o zasięgu lokalnym! nie jest już prawdą w przypadku C ++ 11: - Obiekty powiązane z bieżącym wątkiem z czasem przechowywania wątku są niszczone (tylko C ++ 11). cplusplus.com/reference/cstdlib/exit
Ilendir
7
Oznacza to, thread_localże zostaną wywołane destruktory obiektów. Niszczyciele dla innych obiektów lokalnych nadal nie są wywoływane. ideone.com/Y6Dh3f
HolyBlackCat
3
BTW, i po prostu bądź pedantyczny, a ponieważ ta odpowiedź może być myląca dla czytelników używających C: W przypadku C problem z exit()czystym zamykaniem plików jest w rzeczywistości błędny. Jedynym czasem, w którym dane mogą nie zostać usunięte, jest odwrotny przypadek: tj. Jeśli ktoś korzysta returnz main()i wywołuje setbuf()lub setvbuf()bufor jest zadeklarowany jako automatyczne przechowywanie w main()(jak omówiono w odpowiedzi R. poniżej). Szkoda, że ​​to pytanie jest oznaczone zarówno C, jak i C ++ (i stylem kodowania - to nie jest problem ze stylem!).
Greg A. Woods,
25

Kolejna różnica: exitjest funkcją biblioteki standardowej, więc musisz dołączyć nagłówki i połączyć się ze standardową biblioteką. Aby to zilustrować (w C ++), jest to poprawny program:

int main() { return 0; }

ale do użycia exitpotrzebujesz:

#include <stdlib.h>
int main() { exit(EXIT_SUCCESS); }

Dodatkowo dodaje to dodatkowe założenie: że sprawdzanie exitz mainma takie same skutki uboczne jak zwracanie zera. Jak zauważyli inni, zależy to od tego, jaki rodzaj pliku wykonywalnego budujesz (tj. Kto dzwoni main). Czy kodujesz aplikację korzystającą ze środowiska wykonawczego C? Wtyczka Maya? Usługa Windows? Kierowca? Każdy przypadek będzie wymagał badań, aby sprawdzić, czy exitjest równoważny return. Używanie IMHO, exitgdy naprawdę masz na myśli,return sprawia, że ​​kod jest bardziej mylący. OTOH, jeśli naprawdę masz na myśli exit , to na pewno wykorzystaj to.

jwfearn
źródło
16

Istnieje co najmniej jeden powód, aby preferować exit: jeśli któryś z twoich programów atexitobsługi odnosi się do danych dotyczących czasu automatycznego przechowywania w mainlub jeśli używałeś setvbuflub setbufprzypisałeś do jednego ze standardowych strumieni bufor automatycznego czasu przechowywania main, to wracasz z mainprodukcji niezdefiniowane zachowanie, ale wywołanie exitjest prawidłowe.

Innym potencjalnym zastosowaniem (zwykle zarezerwowanym dla programów zabawkowych) jest wyjście z programu z rekurencyjnymi wywołaniami main.

R .. GitHub ZATRZYMAJ LOD
źródło
1
@Seb nie ma w tym nic specjalnego main()- to po prostu funkcja jak każda inna. Z drugiej strony, ponieważ ma on specjalne wzmiankę w normie, norma musi być dość ostrożna w kwestii tego, jak definiuje, jak main()i rzeczy, które są jej bliskie i drogie. Jednak w końcu standard nie wymaga (i nie musi ) wymagać od kompilatorów robienia czegokolwiek specjalnego na temat automatycznego przechowywania w main(). Przeczytaj uważnie przypis 11 pod akapitem, do którego odwołujesz się w komentarzu.
Greg A. Woods,
1
@ GregA.Woods Interesujące. Wydaje się, że istnieje tekst normatywny sprzeczny z tekstem informacyjnym. Zgodnie z dyrektywami ISO / IEC odniesienie normatywne jest uważane za „niezbędne”, ponieważ - ponieważ informacje są uważane jedynie za uzupełniające… Ponadto użycie słowa „woli” w celu przekazania wymagania jest nieważne; zgodnie z wyżej wymienionym dokumentem (załącznik H). Podsumowując, tekst informacyjny z pewnością jest nieprawidłowy.
autystyczny
2
@Seb: Chodzi oczywiście o to, aby nie zastępować wymagań dotyczących automatycznego przechowywania, a przypis został oczywiście napisany w celu wyjaśnienia tego. Tak, w standardzie C jest nieprecyzyjne, słabe sformułowanie. Każdy, kto to przeczytał, wie o tym. Wiemy również, że komitet zasadniczo nie naprawia takich problemów, ponieważ zamiar jest już oczywisty.
R .. GitHub ZATRZYMAJ LÓD
1
@Seb: To nie jest debata ani sprawa sądowa, aby udowodnić, że masz rację. Celem powinno być jasne zrozumienie, czym jest rzeczywisty język C (zgodnie z zamierzeniami i zaimplementowaniem) oraz wyrażanie tego w odpowiedziach przydatnych dla czytelników. Tekst normatywny jest subtelnie niepoprawny (wbrew intencji tego, co miał wyrazić) w sposób, który zasadniczo został ustalony w przypisie. Jeśli nie jesteś zadowolony z tego, prześlij zgłoszenie usterki, ale nie oczekuj odpowiedzi. Tak właśnie działa WG14 ...
R .. GitHub ZATRZYMAJ LÓD
3
@Seb: Wydaje ci się, że język C można zrozumieć, interpretując tekst standardu w języku naturalnym, tak jakby był on całkowicie rygorystyczny. To po prostu niemożliwe. Specyfikacja zawiera błędy, a WG14 nie marnuje czasu na przepisywanie rzeczy, gdy prosty przypis wyjaśnia, że ​​już wiedzą, że popełnił błąd, ale czytelnik może to zrozumieć.
R .. GitHub ZATRZYMAJ LÓD
5

Zawsze używam, returnponieważ standardowy prototyp dla main()mówi, że zwraca an int.

To powiedziawszy, niektóre wersje standardów zapewniają mainspecjalne traktowanie i zakładają, że zwraca 0, jeśli nie ma wyraźnego returnoświadczenia. Biorąc pod uwagę następujący kod:

int foo() {}
int main(int argc, char *argv[]) {}

G ++ generuje tylko ostrzeżenie foo()i ignoruje brakujący zwrot z main:

% g++ -Wall -c foo.cc
foo.cc: In function int foo()’:
foo.cc:1: warning: control reaches end of non-void function
Alnitak
źródło
Nie wiem o C, ale standard C ++ określa, że ​​jeśli nie zwrócisz wartości w main, zakłada się, że zwróci 0.
Jason Baker
Wygląda na to, że C99 jest taki sam: faq.cprogramming.com/cgi-bin/…
Jason Baker
2
C99 i C ++ zwracają 0, jeśli nie ma instrukcji return, C90 nie.
d0k
To, że funkcja ma deklarowaną wartość zwracaną, nie oznacza, że ​​musisz jej użyć, returnaby zakończyć jej wykonywanie. Wywołanie exit()jest również prawidłowym, a czasem koniecznym sposobem zakończenia wykonywania dowolnej funkcji. Rzeczywiście, jak ja i inni opisaliśmy gdzie indziej, wywoływanie exit()nawet z main()przekazuje o wiele bardziej wyraźną intencję wyjścia z całego procesu, zachowuje automatyczne przechowywanie do momentu zakończenia procesu i ułatwia konserwację podczas przyszłego refaktoryzacji kodu. Dla C stosując returnw main()kiedy intencją jest, aby zakończyć proces jest więc bez wątpienia złą praktyką.
Greg A. Woods,
@ GregA.Woods to twoje zdanie, ale nie zasługuje na głosowanie w dół! To, co napisałem powyżej, jest całkowicie zgodne ze standardem , podczas gdy twój argument to tylko semantyka.
Alnitak,
5

ja SILNIE drugi komentarz na temat korzystania przez R. exit () w celu uniknięcia konieczności automatycznego przechowywania w main()regeneracji zanim program w zasadzie kończy. return X;Oświadczenie w main()nie jest dokładnie równoważne wywołaniu exit(X);, ponieważ dynamiczny przechowywania main()znika, gdy main()powróci, ale nie znikają, jeśli wywołanie exit()jest wykonany zamiast.

Ponadto w języku C lub dowolnym języku podobnym do C returninstrukcja zdecydowanie wskazuje czytelnikowi, że wykonanie będzie kontynuowane w funkcji wywołującej i chociaż ta kontynuacja wykonywania jest zwykle technicznie prawdziwa, jeśli policzy się procedurę uruchamiania C, która wywołała twoją main()funkcję, nie jest dokładnie to, co masz na myśli, kiedy chcesz zakończyć proces.

W końcu, jeśli chcesz zakończyć swój program z jakiejkolwiek innej funkcji, z wyjątkiem tego, main()że musisz wywołaćexit() . Konsekwentne postępowanie w ten main()sposób powoduje, że kod jest znacznie bardziej czytelny, a także ułatwia każdemu ponowne przeliczenie kodu; tzn. kod skopiowany z main()innej funkcji nie będzie źle działał z powodu przypadkowych returninstrukcji, które powinny były być exit()wywołaniami.

Tak więc, łącząc wszystkie te punkty razem, wniosek jest taki, że złym nawykiem , przynajmniej dla C, jest użycie returninstrukcji do zakończenia programu wmain() .

Greg A. Woods
źródło
Ciekawe może być 5.1.2.2.3p1 standardu C ...
autystyczny
Ta odpowiedź jest warta dokładnego rozważenia w przypadku programów typu C, jak wskazano kontekstowo w odpowiedzi. Aby używać z C ++, należy dokładnie rozważyć wszystkie wyżej wymienione zastrzeżenia. W przypadku C ++ sugerowałbym, aby unikać exit(), ale używaj go, jeśli a throwlub abort()alternatywy nie działają w określonym kontekście. Ale szczególnie unikaj exit()w main i używaj return in main zamiast typowej praktyki.
Eljay,
5

Czy exit () robi coś specjalnego, czego nie robi „return”?

W przypadku niektórych kompilatorów dla nietypowych platform exit()może tłumaczyć swój argument na wartość wyjściową programu, podczas gdy zwrot z main()może po prostu przekazać wartość bezpośrednio do środowiska hosta bez żadnego tłumaczenia.

Standard wymaga identycznego zachowania w tych przypadkach (konkretnie, to mówi powrocie coś, co jest int-Kompatybilny ze main()powinno być równoznaczne z wywołaniem exit()tej wartości). Problem polega na tym, że różne systemy operacyjne mają różne konwencje interpretowania wartości wyjściowych. W wielu systemach (WIELE!) 0 oznacza sukces, a wszystko inne jest porażką. Ale przy, powiedzmy, VMS, nieparzyste wartości oznaczają sukces, a nawet te oznaczają porażkę. Jeśli zwróciłeś 0 zmain() , użytkownik VMS zobaczy nieprzyjemną wiadomość o naruszeniu dostępu. W rzeczywistości nie doszło do naruszenia praw dostępu - był to po prostu standardowy komunikat związany z kodem błędu 0.

Potem przyszedł ANSI i pobłogosławił, EXIT_SUCCESSa EXIT_FAILUREjako argumenty można przekazać exit(). Norma mówi również, że exit(0)należy zachowywać się identycznie exit(EXIT_SUCCESS), więc większość implementacji zdefiniować EXIT_SUCCESSdo 0.

Dlatego standard wprowadza cię w VMS, ponieważ nie pozostawia żadnego standardowego sposobu na zwrócenie kodu błędu , który ma wartość 0.

Kompilator VAX / VMS C z wczesnych lat 90. nie interpretował zatem wartości zwracanej main(), po prostu zwracał dowolną wartość do środowiska hosta. Ale jeśli go użyjesz exit(), zrobi to, czego wymaga standard: przetłumacz EXIT_SUCCESS(lub 0) na kod sukcesu i EXIT_FAILUREna ogólny kod błędu. Aby użyć EXIT_SUCCESS, trzeba było przekazać go exit(), z którego nie można było go zwrócić main(). Nie wiem, czy nowsze wersje tego kompilatora zachowały to zachowanie.

Przenośny program C wyglądał tak:

#include <stdio.h>
#include <stdlib.h>

int main() {
  printf("Hello, World!\n");
  exit(EXIT_SUCCESS);  /* to get good return value to OS */
  /*NOTREACHED*/ /* to silence lint warning */
  return 0;  /* to silence compiler warning */
}

Na bok: jeśli dobrze pamiętam, konwencja VMS dla wartości wyjściowych jest bardziej szczegółowa niż nieparzysta / parzysta. W rzeczywistości używa czegoś takiego jak trzy niskie bity do kodowania poziomu ważności. Ogólnie jednak, nieparzyste poziomy dotkliwości wskazywały na sukces lub różne informacje, a parzyste wskazywały błędy.

Adrian McCarthy
źródło
Niektóre stare kompilatory pre-ANSI mógłby traktować wartość returnedprzez mainodmiennie od wartości przekazanego exit- ale średnia specjalnie mówi: „Jeśli typ powrót mainfunkcji jest kompatybilny z typem int, powrót z pierwszego połączenia do mainfunkcji jest odpowiednik wywołania exitfunkcji z wartością zwróconą przez mainfunkcję jako argument ". To jest C11; C89 / C90 miał prawie takie samo brzmienie.
Keith Thompson
W rzeczy samej. Niemniej jednak niektóre kompilatory z ery ANSI nie zrozumiały tego poprawnie i wymagały jawnego użycia komendy exit, aby uzyskać prawidłową wartość zwracaną do środowiska hosta. Ponieważ standard (nawet wtedy) wymaga, aby 0 było traktowane tak samo jak EXIT_SUCCESS, nie było możliwości zwrócenia specyficznego dla platformy stanu awarii z wartością 0, co może być przyczyną, dla której niektóre kompilatory ery traktowane jako powrót-z-głównego i exit()inaczej.
Adrian McCarthy
Czy masz na to powód? Osobnym problemem jest to, czy którykolwiek z obecnych kompilatorów ma ten konkretny błąd. Twoja odpowiedź dotyczy wyrażenia w czasie teraźniejszym.
Keith Thompson
To uczciwa krytyka. Zmieniłem sformułowanie, aby ograniczyć zakres do konkretnego przypadku, o którym wiem.
Adrian McCarthy
0

W C powrót z mainjest dokładnie tym samym, co wywołanie exitz tą samą wartością.

Sekcja 5.1.2.2.3 normy C stanowi:

Jeśli typ zwracany przez funkcję główną jest typem zgodnym z int, powrót z początkowego wywołania do funkcji głównej jest równoważny z wywołaniem funkcji wyjścia z wartością zwróconą przez funkcję główną jako argument ; 11) osiągnięcie} kończącego główną funkcję zwraca wartość 0. Jeśli typ zwracany jest niezgodny z int, status zakończenia zwracany do środowiska hosta jest nieokreślony.

Reguły dla C ++ są nieco inne, jak wspomniano w innych odpowiedziach.

dbush
źródło
-1

W rzeczywistości istnieje różnica pomiędzy exit(0)i return(0)wewnątrz main- gdy twoja mainfunkcja jest wywoływana wiele razy.

Poniższy program

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  if (argc == 0)
    return(0);
  printf("%d", main(argc - 1, argv));
}

Uruchom jako

./program 0 0 0 0

Spowoduje następujące wyniki:

00000

Jednak ten:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  if (argc == 0)
    exit(0);
  printf("%d", main(argc - 1, argv));
}

Nie wydrukuje niczego bez względu na argumenty.

Jeśli masz pewność, że nikt nigdy nie zadzwoni mainwprost, nie jest to technicznie duża różnica w ogóle, ale utrzymanie wyraźniejszego kodu exitwyglądałoby znacznie lepiej. Jeśli z jakiegoś powodu chcesz zadzwonić main- powinieneś dostosować go do swoich potrzeb.

Mówiąc o C.

radrow
źródło