Gibt es einen Unterschied zwischen return n und exit (n) in C?

9

Gibt es einen Unterschied zwischen return n(in der mainFunktion) und exit(n)in C? Ist es durch C- oder POSIX-Standards definiert oder hängt es vom Betriebssystem oder Compiler ab?

Thomas Owens
quelle

Antworten:

5

In den meisten Fällen gibt es keinen Unterschied, aber hier ist ein C-Programm, das sich wahrscheinlich unterschiedlich verhält, je nachdem, ob es verwendet wird return 0;oder exit(0);:

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

static char *message;

void cleanup(void) {
    printf("message = \"%s\"\n", message);
}

int main(void) {
    char local_message[] = "hello, world";
    message = local_message;
    atexit(cleanup);
#ifdef USE_EXIT
    puts("exit(0);");
    exit(0);
#else
    puts("return 0;");
    return 0;
#endif
}

Aufgrund des atexit()Aufrufs wird die Funktion entweder exit(0);oder aufgerufen. Der Unterschied besteht darin, dass beim Aufrufen des Programms die Bereinigung erfolgt, während der "Aufruf" von noch aktiv ist, sodass das Objekt weiterhin vorhanden ist. Das Ausführen beendet jedoch sofort den Aufruf von und ruft dann die Funktion auf. Da (über den globalen Zeiger) auf ein Objekt verwiesen wird, das lokal zugeordnet ist und dieses Objekt nicht mehr vorhanden ist, ist das Verhalten undefiniert.return 0;cleanupexit(0);main()local_messagereturn 0;main()cleanup()cleanup()messagemain

Hier ist das Verhalten, das ich auf meinem System sehe:

$ gcc -DUSE_EXIT c.c -o c && ./c
exit(0);
message = "hello, world"
$ gcc c.c -o c && ./c
return 0;
message = ""
$ 

Das Ausführen des Programms ohne -DUSE_EXITkann alles tun, einschließlich Absturz oder Drucken "hello, world"(wenn der von verwendete Speicher local_messagenicht überlastet ist).

In der Praxis zeigt sich dieser Unterschied jedoch nur, wenn lokal definierte Objekte main()außerhalb sichtbar gemacht werden, main()indem Zeiger darauf gespeichert werden. Dies könnte plausibel passieren für argv. (Experimente auf meinem System zeigen, dass die Objekte, auf die nach argvund nach verwiesen wird, *argvnach der Rückkehr weiterhin vorhanden sind main(), aber darauf sollten Sie sich nicht verlassen.)

Keith Thompson
quelle
16
  • Für C
    Der Standard besagt, dass eine Rückkehr vom ersten Aufruf zu main dem Aufruf von exit entspricht. Es ist jedoch nicht zu erwarten, dass eine Rückgabe von main funktioniert, wenn während der Bereinigung möglicherweise lokale Daten zu main benötigt werden.

  • Für C ++

Wenn exit (0) zum Beenden des Programms verwendet wird, werden Destruktoren für nicht statische Objekte mit lokalem Gültigkeitsbereich nicht aufgerufen. Destruktoren werden jedoch aufgerufen, wenn return 0 verwendet wird.

Programm 1 - - verwendet exit (0) zum Beenden

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

using namespace std;

class Test {
public:
  Test() {
    printf("Inside Test's Constructor\n");
  }

  ~Test(){
    printf("Inside Test's Destructor");
    getchar();
  }
};

int main() {
  Test t1;

  // using exit(0) to exit from main
  exit(0);
}

Ausgabe: Im Konstruktor des Tests

Programm 2 - verwendet return 0 zum Beenden

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

using namespace std;

class Test {
public:
  Test() {
    printf("Inside Test's Constructor\n");
  }

  ~Test(){
    printf("Inside Test's Destructor");
  }
};

int main() {
  Test t1;

   // using return 0 to exit from main
  return 0;
}

Ausgabe: Innerhalb des Testkonstruktors
Innerhalb des Testdestruktors

Das Aufrufen von Destruktoren ist manchmal wichtig, beispielsweise wenn der Destruktor über Code verfügt, um Ressourcen wie das Schließen von Dateien freizugeben.

Beachten Sie, dass statische Objekte auch dann bereinigt werden, wenn wir exit () aufrufen. Siehe zum Beispiel folgendes Programm.

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

using namespace std;

class Test {
public:
  Test() {
    printf("Inside Test's Constructor\n");
  }

  ~Test(){
    printf("Inside Test's Destructor");
    getchar();
  }
};

int main() {
  static Test t1;  // Note that t1 is static

  exit(0);
}

Ausgabe: Innerhalb des Testkonstruktors
Innerhalb des Testdestruktors

Vaibhav Agarwal
quelle
Das Schließen von Dateien ist kein gutes Beispiel für einen wichtigen Destruktor, der beim Beenden ausgelöst wird, da Dateien beim Beenden des Programms sowieso geschlossen werden.
Winston Ewert
1
@WinstonEwert: Richtig, aber möglicherweise sind Puffer auf Anwendungsebene vorhanden, die noch geleert werden müssen.
Philipp
1
Die Frage erwähnt C ++ nirgendwo ...
tdammers
Ich kenne keine der beiden Sprachen, also vergib mir, aber diese Antwort lässt mich denken, dass exit wie C # 's Failfast ist. Wird in C ++ in einem Versuch ein Exit ausgeführt, um ein endgültiges auszuführen?
Jimmy Hoffa
@ JimmyHoffa, c ++ hat nichtfinally
Winston Ewert
6

Es ist erwähnenswert, dass der C-Standard (C99) zwei Arten von Ausführungsumgebungen definiert: Freistehende Umgebung und Gehostete Umgebung . Die freistehende Umgebung ist eine C-Umgebung, die die C-Bibliotheken nicht unterstützt und für eingebettete Anwendungen und dergleichen vorgesehen ist. Eine AC-Umgebung, die die C-Bibliotheken unterstützt, wird als gehostete Umgebung bezeichnet.

Laut C99 ist in einer freistehenden Umgebung die Beendigung eines Programms definiert. Wenn also die Implementierung definiert main, return nund exit, ist ihr Verhalten so, wie es in dieser Implementierung definiert ist.

C99 definiert das Verhalten der gehosteten Umgebung als:

Wenn der Rückgabetyp der Hauptfunktion ein mit ihr kompatibler Typ ist, entspricht eine Rückgabe vom ersten Aufruf der Hauptfunktion dem Aufrufen der Exit-Funktion mit dem von der Hauptfunktion als Argument zurückgegebenen Wert. Das Erreichen des}, das die Hauptfunktion beendet, gibt den Wert 0 zurück. Wenn der Rückgabetyp nicht mit int kompatibel ist, ist der an die Hostumgebung zurückgegebene Beendigungsstatus nicht angegeben.

Das D
quelle
1
Und es macht wirklich keinen Sinn, exit () aus einer freistehenden Umgebung heraus aufzurufen. Das eingebettete Äquivalent von exit () wäre, das Programm in eine ewige Schleife zu hängen und dann auf das Timeout des Watchdogs zu warten.
0

Aus der Sicht des C-Standards nicht wirklich, außer returneine Aussage und exit()eine Funktion zu sein. In beiden Fällen werden alle mit registrierten Funktionen atexit()aufgerufen und das Programm beendet.

Es gibt einige Situationen, auf die Sie achten sollten:

  • Rekursion in main(). Während es in der Praxis selten zu sehen ist, ist es in C legal (C ++ verbietet es ausdrücklich.)
  • Wiederverwendung von main(). Manchmal wird ein vorhandenes main()in etwas anderes umbenannt und von einem neuen aufgerufen main().

Die Verwendung von exit()führt zu einem Fehler, wenn einer dieser Fehler auftritt, nachdem Sie den Code geschrieben haben, insbesondere wenn er nicht abnormal beendet wird. Um dies zu vermeiden, ist es eine gute Idee, die Gewohnheit zu haben, sie main()als die Funktion zu behandeln , die sie ist, und sie zu verwenden, returnwenn Sie möchten, dass sie endet.

Blrfl
quelle