Ответ 1
- return - это инструкция языка, возвращающегося из вызова функции.
- exit - системный вызов (не оператор языка), который завершает текущий процесс.
Единственный случай, когда оба делают (почти) то же самое в функции main()
, поскольку возврат из main выполняет exit()
.
Пример с return
:
#include <stdio.h>
void f(){
printf("Executing f\n");
return;
}
int main(){
f();
printf("Back from f\n");
}
Если вы выполните эту программу, она напечатает:
Executing f Back from f
Другой пример для exit()
:
#include <stdio.h>
#include <stdlib.h>
void f(){
printf("Executing f\n");
exit(0);
}
int main(){
f();
printf("Back from f\n");
}
Если вы выполните эту программу, она напечатает:
Executing f
Вы никогда не получите "Назад с f". Также обратите внимание на #include <stdlib.h>
, необходимую для вызова библиотечной функции exit()
.
Также обратите внимание, что параметр exit()
является целым числом (это статус возврата процесса, который может получить процесс запуска, обычное использование - 0 для успеха или любое другое значение для ошибки).
Параметр оператора return - это любой тип возвращаемого значения функции. Если функция возвращает void, вы можете опустить возврат в конце функции.
Последняя точка, exit()
представлена в двух вариантах _exit()
и exit()
. Разница между формами заключается в том, что exit()
(и возврат из основного) вызывает функции, зарегистрированные с использованием atexit()
или on_exit()
, прежде чем действительно завершить процесс, а _exit()
(из #include <unistd.h>
или его синонимом _Exit от #include <stdlib.h>
) немедленно прекращает процесс.
Теперь есть также проблемы, характерные для С++.
С++ выполняет гораздо большую работу, чем C, когда он выходит из функций (return
-ing). В частности, он вызывает деструкторы локальных объектов, выходящих из области видимости. В большинстве случаев программисты не будут заботиться о состоянии программы после остановки процесса, поэтому это не имеет большого значения: выделенная память будет освобождена, файл закрыт и т.д. Но это может иметь значение, если ваш деструктор выполняет IO. Например, автоматическое создание С++ OStream
локально не будет очищено при вызове для выхода, и вы можете потерять некоторые незафиксированные данные (с другой стороны, статический OStream
будет сброшен).
Это не произойдет, если вы используете старые добрые потоки C FILE*
. Они будут сброшены на exit()
. На самом деле правило такое же, что и для зарегистрированных функций выхода, FILE*
будет сбрасываться на всех нормальных окончаниях, включая exit()
, но не вызывает _exit()
или abort().
Вы также должны иметь в виду, что С++ предоставляет третий способ выйти из функции: выброс исключения. Этот способ выхода из функции вызовет деструктор. Если он не попадает в цепочку вызывающих абонентов, исключение может перейти к функции main() и завершить процесс.
Деструкторы статических объектов С++ (globals) будут вызываться, если вы вызываете либо return
из main()
, либо exit()
в любом месте вашей программы. Они не будут вызваны, если программа завершается с помощью _exit()
или abort()
. abort()
в основном полезен в режиме отладки с целью немедленной остановки программы и получения трассировки стека (для анализа post mortem). Обычно он скрывается за макросом assert()
, только активным в режиме отладки.
Когда функция exit() полезна?
exit()
означает, что вы хотите немедленно остановить текущий процесс. Это может быть полезно для управления ошибками, когда мы сталкиваемся с какой-то безвозвратной проблемой, которая больше не позволит вашему коду делать что-нибудь полезное. Это часто бывает удобно, когда поток управления сложный, и коды ошибок должны распространяться повсюду. Но имейте в виду, что это неправильная практика кодирования. Тише завершение процесса в большинстве случаев должно быть предпочтительнее худшее поведение и фактическое управление ошибками (или на С++ с использованием исключений).
Прямые вызовы exit()
особенно плохи, если они выполняются в библиотеках, поскольку они будут обрекать пользователя библиотеки, и это должен быть выбор пользователя библиотеки, чтобы реализовать какое-то исправление ошибок или нет. Если вам нужен пример того, почему вызов exit()
из библиотеки плох, это приводит, например, к людям, которые задают этот вопрос.
Существует неоспоримое законное использование exit()
в качестве способа завершения дочернего процесса, запущенного fork() в операционных системах, поддерживающих его. Возвращаясь к коду перед fork(), как правило, это плохая идея. Это объяснение того, почему функции семейства exec() никогда не возвращаются к вызывающему.