Czy jest jakaś różnica między zwrotem n a wyjściem (n) w C?

9

Czy jest jakaś różnica między return n(w mainfunkcji) a exit(n)w C? Czy jest zdefiniowany przez standardy C lub POSIX, czy zależy od systemu operacyjnego lub kompilatora?

Thomas Owens
źródło

Odpowiedzi:

5

W większości przypadków nie ma różnicy, ale oto program C, który prawdopodobnie zachowuje się inaczej w zależności od tego, czy używa return 0;lub 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
}

Ze względu na atexit()rozmowy, albo exit(0);czy return 0;spowoduje, że cleanupfunkcja się powoływać. Różnica polega na tym, że jeśli program wywołuje exit(0);, czyszczenie odbywa się, gdy „wywołanie” main()jest nadal aktywne, więc local_messageobiekt nadal istnieje. Wykonanie powoduje return 0;jednak natychmiastowe zakończenie wywołania, main()a następnie wywołanie cleanup()funkcji. Ponieważ cleanup()odnosi się (za pomocą messagewskaźnika globalnego ) do obiektu przydzielonego lokalnie maini obiekt ten już nie istnieje, zachowanie jest niezdefiniowane.

Oto zachowanie, które widzę w moim systemie:

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

Uruchomienie programu bez -DUSE_EXITniczego może zrobić, włączając awarię lub drukowanie "hello, world"(jeśli local_messagezdarza się, że używana pamięć nie jest blokowana).

W praktyce jednak ta różnica pojawia się tylko wtedy, gdy obiekty zdefiniowane lokalnie wewnątrz main()są widoczne na zewnątrz main()poprzez zapisanie do nich wskaźników. Możliwe, że tak się stanie argv. (Eksperyment w moim systemie pokazuje, że obiekty wskazywane przez argvi przez *argvistnieją po powrocie main(), ale nie powinieneś na tym polegać).

Keith Thompson
źródło
16
  • Dla C
    Standard mówi, że powrót z pierwszego połączenia do głównego jest równoważny z wywołaniem wyjścia. Jednak nie można oczekiwać powrotu z main, jeśli dane lokalne do main mogą być potrzebne podczas czyszczenia.

  • Dla C ++

Kiedy exit (0) jest używane do wyjścia z programu, niszczyciele dla obiektów niestacjonarnych o zasięgu lokalnym nie są wywoływane. Ale destruktory są wywoływane, jeśli użyty jest return 0.

Program 1 - - używa wyjścia (0) do wyjścia

#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);
}

Wyjście: konstruktor testu wewnętrznego

Program 2 - używa return 0, aby wyjść

#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;
}

Dane wyjściowe: Konstruktor
testu wewnętrznego Inside Destructor testu

Wywoływanie destruktorów jest czasem ważne, na przykład, jeśli destruktor ma kod do zwalniania zasobów takich jak zamykanie plików.

Zauważ, że obiekty statyczne zostaną wyczyszczone, nawet jeśli wywołamy exit (). Na przykład patrz następujący program.

#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);
}

Dane wyjściowe: Konstruktor
testu wewnętrznego Inside Destructor testu

Vaibhav Agarwal
źródło
Zamykanie plików nie jest tak naprawdę dobrym przykładem ważnego niszczyciela, który jest uruchamiany podczas wychodzenia, ponieważ pliki i tak zostaną zamknięte przy wyjściu z programu.
Winston Ewert
1
@WinstonEwert: Prawda, ale mogą istnieć bufory na poziomie aplikacji, które nadal wymagają opróżnienia.
Philipp
1
Pytanie nie wspomina o C ++ nigdzie ...
tdammers
Nie znam żadnego języka, więc wybacz mi, ale ta odpowiedź sprawia, że ​​myślę, że wyjście jest jak niezawodny C #, więc w C ++ wyjście przy próbie wykonania w końcu?
Jimmy Hoffa
@JimmyHoffa, c ++ nie mafinally
Winston Ewert
6

Warto zauważyć, że standard C (C99) definiuje dwa typy środowisk wykonawczych: środowisko wolnostojące i środowisko hostowane . Środowisko wolnostojące to środowisko C, które nie obsługuje bibliotek C i jest przeznaczone dla aplikacji osadzonych i tym podobnych. Środowisko AC obsługujące biblioteki C nazywa się środowiskiem hostowanym.

C99 mówi, że w środowisku wolnostojącym zakończenie programu jest zdefiniowane jako implementacja. Zatem jeśli implementacja definiuje main, return na exitich zachowania są takie, jak zdefiniowano w tej implementacji.

C99 definiuje zachowanie środowiska hostowanego jako,

Jeśli typ zwracany przez funkcję główną jest typem zgodnym z nią, 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; osiągnięcie}, które kończy działanie funkcji głównej, zwraca wartość 0. Jeśli typ zwracany jest niezgodny z int, status zakończenia zwracany do środowiska hosta jest nieokreślony.

theD
źródło
1
I naprawdę nie ma sensu wywoływać exit () z wolnostojącego środowiska. Osadzony odpowiednik exit () miałby zawiesić program w wiecznej pętli, a następnie poczekać, aż limit czasu wygaśnie.
0

Z punktu widzenia standardu C, nie do końca, poza returnbyciem wyrażeniem i exit()funkcją. Albo spowoduje wywołanie wszystkich zarejestrowanych funkcji, atexit()po których nastąpi zakończenie programu.

Istnieje kilka sytuacji, na które należy uważać:

  • Rekurencja w main(). Choć rzadko spotykany w praktyce, jest legalny w C. (C ++ wyraźnie tego zabrania).
  • Ponowne użycie main(). Czasami istniejący main()zostanie przemianowany na coś innego i zostanie nazwany przez nowego main().

Użycie exit()spowoduje wprowadzenie błędu, jeśli którykolwiek z nich wystąpi po napisaniu kodu, zwłaszcza jeśli nie zakończy się on nieprawidłowo. Aby tego uniknąć, warto mieć w zwyczaju traktować main()tę funkcję, jaką jest, i używać, returnkiedy chcesz, aby się zakończyła.

Blrfl
źródło