Was ist der Unterschied zwischen Ausgang und Rückkehr?

Lesezeit: 10 Minuten

Shonas Benutzeravatar
shona

Was ist der Unterschied zwischen return- und exit-Anweisungen in der C-Programmierung, wenn sie von irgendwo in einem C-Programm aufgerufen werden?

  • Ich habe das „Als Duplikat schließen“ entfernt, da das ausgewählte Duplikat sowohl mit C als auch mit C++ gekennzeichnet ist und es nicht nötig ist, die Leute mit den C++-Problemen zu verwirren (die sich von den C-Problemen unterscheiden, obwohl sie ihnen vage ähnlich sind). Das Duplikat war return-Anweisung vs exit() in main()?

    – Jonathan Leffler

    20. April um 3:09 Uhr

Benutzeravatar von kriss
kriss

  • Rückkehr kehrt von der aktuellen Funktion zurück; es ist ein Sprachschlüsselwort wie for oder break.
  • Ausfahrt() beendet das gesamte Programm, egal von wo aus Sie es aufrufen. (Nach dem Spülen der stdio-Puffer usw.).

Der einzige Fall, in dem beide (fast) dasselbe tun, ist in der main() Funktion, als eine Rückkehr von der Hauptleitung an führt exit().

In den meisten C-Implementierungen main ist eine echte Funktion, die von einem Startcode aufgerufen wird, der so etwas tut int ret = main(argc, argv); exit(ret);. Der C-Standard garantiert, dass etwas Äquivalentes passiert, wenn main zurückgibt, jedoch von der Implementierung verarbeitet wird.

Beispiel mit return:

#include <stdio.h>

void f(){
    printf("Executing f\n");
    return;
}

int main(){
    f();
    printf("Back from f\n");
}

Wenn Sie dieses Programm ausführen, druckt es:

Executing f
Back from f

Ein weiteres Beispiel für exit():

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

void f(){
    printf("Executing f\n");
    exit(0);
}

int main(){
    f();
    printf("Back from f\n");
}

Wenn Sie dieses Programm ausführen, druckt es:

Executing f

Du bekommst nie “Zurück von f”. Beachten Sie auch die #include <stdlib.h> notwendig, um die Bibliotheksfunktion aufzurufen exit().

Beachten Sie auch, dass der Parameter von exit() ist eine Ganzzahl (es ist der Rückgabestatus des Prozesses, den der Launcher-Prozess erhalten kann; die herkömmliche Verwendung ist 0 für Erfolg oder jeder andere Wert für einen Fehler).

Der Parameter der return-Anweisung ist unabhängig vom Rückgabetyp der Funktion. Wenn die Funktion void zurückgibt, können Sie die Rückgabe am Ende der Funktion weglassen.

Letzter Punkt, exit() kommen in zwei Geschmacksrichtungen _exit() und exit(). Der Unterschied zwischen den Formen ist der exit() (und Rückkehr von main) ruft mit registrierte Funktionen auf atexit() oder on_exit() bevor der Vorgang wirklich beendet wird, während _exit() (aus #include <unistd.h>oder sein Synonym _Exit from #include <stdlib.h>) beendet den Vorgang sofort.

Jetzt gibt es auch Probleme, die spezifisch für C++ sind.

C++ führt viel mehr Arbeit aus als C, wenn es Funktionen verlässt (return-ing). Insbesondere ruft es Destruktoren von lokalen Objekten auf, die den Gültigkeitsbereich verlassen. In den meisten Fällen kümmern sich Programmierer nicht viel um den Zustand eines Programms, nachdem der Prozess gestoppt wurde, daher würde es keinen großen Unterschied machen: Zugewiesener Speicher wird freigegeben, Dateiressourcen geschlossen und so weiter. Aber es kann wichtig sein, ob Ihr Destruktor IOs ausführt. Zum Beispiel automatisches C++ OStream lokal erstellte Dateien werden bei einem Aufruf zum Beenden nicht geleert und Sie verlieren möglicherweise einige nicht geleerte Daten (andererseits statische OStream wird gespült).

Dies wird nicht passieren, wenn Sie das gute alte C verwenden FILE* Ströme. Diese werden gespült exit(). Eigentlich ist die Regel die gleiche wie für registrierte Exit-Funktionen, FILE* wird bei allen normalen Terminierungen gespült, einschließlich exit()aber nicht Anrufe zu _exit() oder abbrechen().

Sie sollten auch bedenken, dass C++ einen dritten Weg bietet, um aus einer Funktion herauszukommen: das Auslösen einer Ausnahme. Diese Art, eine Funktion zu verlassen Wille Destruktor aufrufen. Wenn sie nirgendwo in der Aufruferkette abgefangen wird, kann die Ausnahme an die Funktion main() gehen und den Prozess beenden.

Destruktoren statischer C++-Objekte (Globals) werden aufgerufen, wenn Sie eines von beiden aufrufen return aus main() oder exit() überall in Ihrem Programm. Sie werden nicht aufgerufen, wenn das Programm mit beendet wird _exit() oder abort(). abort() ist hauptsächlich im Debug-Modus nützlich, um das Programm sofort zu stoppen und einen Stack-Trace (für Post-Mortem-Analysen) zu erhalten. Es ist normalerweise hinter dem versteckt assert() Makro nur im Debug-Modus aktiv.

Wann ist exit() nützlich?

exit() bedeutet, dass Sie den aktuellen Prozess sofort stoppen möchten. Es kann für die Fehlerverwaltung von Nutzen sein, wenn wir auf ein nicht behebbares Problem stoßen, das es Ihrem Code nicht mehr ermöglicht, irgendetwas Nützliches zu tun. Es ist oft praktisch, wenn der Kontrollfluss kompliziert ist und Fehlercodes ganz nach oben weitergegeben werden müssen. Beachten Sie jedoch, dass dies eine schlechte Programmierpraxis ist. Das stillschweigende Beenden des Prozesses ist in den meisten Fällen das schlechtere Verhalten und die eigentliche Fehlerverwaltung sollte bevorzugt werden (oder in C++ mit Ausnahmen).

Direktanrufe an exit() sind besonders schlecht, wenn sie in Bibliotheken durchgeführt werden, da dies den Bibliotheksbenutzer zum Scheitern bringen wird und es die Wahl des Bibliotheksbenutzers sein sollte, eine Art Fehlerbehebung zu implementieren oder nicht. Wenn Sie ein Beispiel dafür wollen, warum Sie anrufen exit() aus einer Bibliothek ist schlecht, es führt zum Beispiel dazu, dass Leute diese Frage stellen.

Es gibt eine unbestrittene legitime Verwendung von exit() als Möglichkeit, einen untergeordneten Prozess zu beenden, der von fork() auf Betriebssystemen gestartet wurde, die dies unterstützen. Es ist normalerweise eine schlechte Idee, vor fork() zum Code zurückzukehren. Dies ist der Grund dafür, warum Funktionen der exec()-Familie niemals an den Aufrufer zurückkehren.

  • exit() ist kein Systemaufruf

    anon

    12. August 2010 um 12:25 Uhr

  • Ich benutze normalerweise return in main(). Natürlich benutze ich return 0; am Ende von main() — Ich benutze manchmal exit(); im Körper der Funktion. Ich mag die C99-Regel über das Herunterfallen am Ende von nicht main() gleichwertig sein return 0; Am Ende; es war ein dummer Spezialfall (obwohl C++ den Schaden angerichtet hat).

    – Jonathan Leffler

    19. Januar 2014 um 0:20 Uhr

  • Hinweis: Der C11-Standard hat zusätzliche Funktionen at_quick_exit(), _Exit() (auch in C99) und quick_exit(). Das _exit() Die Funktion stammt von POSIX, ist aber im Wesentlichen identisch mit _Exit().

    – Jonathan Leffler

    19. Januar 2014 um 1:38 Uhr

  • Wenn Sie ein Beispiel haben wollten, um zu veranschaulichen, warum das Beenden in einer Bibliothek schlecht ist: Es bringt die Leute dazu, diese Frage zu stellen: stackoverflow.com/q/34043652/168175. Und dann vermutlich entweder den Hack verwenden oder sich ohne guten Grund für IPC entscheiden

    – Flexo

    6. September 2016 um 7:29 Uhr


  • @Milan: Einige IO werden gepuffert, dies ist der Fall für OStream. In diesem Zusammenhang bedeutet gespült, dass die Daten tatsächlich an die Konsole gesendet/auf die Festplatte geschrieben und nicht nur im Puffer gehalten wurden. Nicht geleert bedeutet, dass sich die Daten noch in einigen Speicherpuffern in der Anwendung befinden, aber noch nicht an das System gesendet wurden. In C++-Dokumenten nennen sie das zugrunde liegende Systemobjekt, an das Daten gesendet werden, eine “kontrollierte Sequenz”. Es gibt eine explizite Flush-Methode auf OStream, um das zu tun.

    – kriss

    17. Dezember 2020 um 3:59 Uhr

Ich habe zwei Programme geschrieben:

int main(){return 0;}

und

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

Nach der Ausführung gcc -S -O1. Hier, was ich beim Zusammenbau gefunden habe (nur wichtige Teile):

main:
    movl    $0, %eax    /* setting return value */
    ret                 /* return from main */

und

main:
    subq    $8, %rsp    /* reserving some space */
    movl    $0, %edi    /* setting return value */
    call    exit        /* calling exit function */
                        /* magic and machine specific wizardry after this call */

Also mein Fazit ist: verwenden return wann du kannst und exit() wenn du brauchst.

  • Dies ist eine sehr gute Antwort, abgesehen von der Schlussfolgerung; IMHO motiviert es das Gegenteil: Wir können davon ausgehen, dass die ret Die Anweisung kehrt zu einem Punkt zurück, an dem möglicherweise zusätzliche Arbeit geleistet wird, aber am Ende wird die Funktion exit() trotzdem aufgerufen – oder wie könnte vermieden werden, das zu tun, was exit() tut? Das bedeutet, dass ersteres nur einige zusätzliche “duplizierte” Arbeiten ohne Vorteil gegenüber der zweiten Lösung erledigt, da exit() sowieso ganz am Ende aufgerufen wird.

    – max

    18. März 2019 um 19:45 Uhr

  • @Max: Es ist nicht verboten, main rekursiv aus Ihrem eigenen Programm aufzurufen. Von nun an sollten Sie nicht davon ausgehen, dass die Rückkehr von main sofort beendet wird, dies würde die Codesemantik brechen. In einigen (seltenen) Fällen ist es sogar nützlich. Zum Beispiel, um etwas Kontext vorzubereiten/zu löschen, bevor main aufgerufen wird, nachdem der Code-Einstiegspunkt auf etwas anderes als main geändert wurde. Dies könnte sogar durch eine Code-Injection-Methode zur Laufzeit erfolgen. Natürlich, wenn es Ihr Programm ist, tun Sie, was Sie bevorzugen oder glauben, dass es einfacher zu lesen ist.

    – kriss

    3. April 2019 um 9:13 Uhr

  • @kriss: Dies ist ein ausgezeichneter Punkt, der zuvor noch nicht erwähnt wurde. Obwohl ich denke, dass es äußerst selten vorkommt, dass main() rekursiv aufgerufen wird, verdeutlicht diese Möglichkeit IMHO besser als alles andere den Unterschied zwischen return und exit(0).

    – max

    11. April 2019 um 0:35 Uhr

Benutzeravatar von Dumb Guy
Dummkopf

In C gibt es keinen großen Unterschied, wenn es in der Startfunktion des Programms verwendet wird (was sein kann main(), wmain(), _tmain() oder der von Ihrem Compiler verwendete Standardname).

Wenn du return in main()die Kontrolle geht zurück an die _start() Funktion in der C-Bibliothek, die ursprünglich Ihr Programm gestartet hat, die dann aufruft exit() Sowieso. Es ist also eigentlich egal, welchen du verwendest.

  • Es ist wichtig. exit() beendet das Programm sofort, egal wo es aufgerufen wird. return beendet nur die aktuelle Funktion. Der einzige Ort, an dem sie dasselbe tun, ist in main ()

    Benutzer47589

    11. August 2010 um 23:17 Uhr

  • Danke, ich habe den Wortlaut korrigiert. Es ist nicht unbedingt nur in main(), da nicht alle Compiler denselben Funktionsnamen für die Startup-Funktion verwenden.

    – Dummkopf

    11. August 2010 um 23:23 Uhr

  • Ich schätze, Sie schreiben alle Ihre Programme in einer großen Hauptfunktion? 😉

    – CJohnson

    12. August 2010 um 10:41 Uhr

  • Die Antwort ist nicht vollständig, aber dennoch informativ.

    – Jagdisch

    26. Juni 2015 um 2:56 Uhr

In den meisten Fällen gibt es in einem C-Programm keinen Unterschied zwischen der Verwendung von return und anrufen exit() zu beenden main().

Der Zeitpunkt, an dem es einen Unterschied gibt, ist, wenn Sie Code erstellt haben, der ausgeführt wird, nachdem Sie von zurückgekehrt sind main() das auf lokalen Variablen beruht main(). Eine Möglichkeit, die sich manifestiert, ist mit setvbuf():

int main(void)
{
    char buffer[BUFSIZ];
    setvbuf(stdout, buffer, _IOFBF, BUFSIZ);
    …code using stdout…
    return 0;
}

In diesem Beispiel wird der Puffer über bereitgestellt setvbuf() geht aus dem Geltungsbereich, wenn main() zurück, aber der Code, der spült und schließt stdout wird versuchen, diesen Puffer zu verwenden. Dies führt zu undefiniertem Verhalten.

Der andere Mechanismus ist das Aufrufen atexit() mit einer Funktion, die auf Daten von zugreift main() — über einen Zeiger. Dies ist schwieriger einzurichten als die Funktionen, die über die aufgerufen werden atexit() Mechanismus werden keine Argumente angegeben. Sie müssen also so etwas tun:

static void *at_exit_data = 0;

static void at_exit_handler(void)
{
    char *str = at_exit_data;
    printf("Exiting: %s\n", str);
}

int main(void);
{
    char buffer[] = "Message to be printed via functions registered with at_exit()";
    at_exit_data = buffer;
    at_exit(at_exit_handler);
    …processing…
    return 0;
}

Wieder zeigte der Puffer auf by at_exit_data nicht mehr existiert, wenn das Programm zurückkehrt main() und so ruft die Handler-Funktion undefiniertes Verhalten auf.

Es gibt eine verwandte Funktion, at_quick_exit()aber die damit registrierten Funktionen werden nur aufgerufen, wenn die quick_exit() Funktion aufgerufen wird, was verhindert, dass die Funktionen danach aufgerufen werden main() kehrt zurück.

die return-Anweisung verlässt die aktuelle Funktion und exit() verlässt das Programm

they are the same when used in main() function

Auch return ist eine Anweisung, während exit() eine Funktion ist, die die Header-Datei stdlb.h erfordert

  • Sie sind gleich wenn main wird von einer der Funktionen aufgerufen in Ihr Programm. Das ist selten; Die meisten Programme verwenden kein rekursives oder wiedereintretendes main, aber wenn wir über Sprachdetails sprechen, dann ist das eine Möglichkeit.

    – Peter Cordes

    12. Juni 2021 um 15:23 Uhr

  • Sie sind gleich wenn main wird von einer der Funktionen aufgerufen in Ihr Programm. Das ist selten; Die meisten Programme verwenden kein rekursives oder wiedereintretendes main, aber wenn wir über Sprachdetails sprechen, dann ist das eine Möglichkeit.

    – Peter Cordes

    12. Juni 2021 um 15:23 Uhr

1421960cookie-checkWas ist der Unterschied zwischen Ausgang und Rückkehr?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy