В чем разница между выходом и возвратом?

В чем разница между операцией return и exit в программировании на C при вызове из любой точки программы C?

Ответ 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() никогда не возвращаются к вызывающему.

Ответ 2

Я написал две программы:

int main(){return 0;}

и

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

После выполнения gcc -S -O1. Вот что я нашел, наблюдая при сборке (только важные части):

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

и

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 */

Итак, мой вывод: используйте return, когда можете, и exit(), когда вам нужно.

Ответ 3

В C нет большой разницы при использовании в стартовой функции программы (которая может быть main(), wmain(), _tmain() или имя по умолчанию, используемое вашим компилятором).

Если вы return в main(), управление возвращается к функции _start() в библиотеке C, которая изначально запускала вашу программу, а затем вызывает exit(). Так что не важно, какой из них вы используете.

Ответ 4

оператор возврата выходит из текущей функции, а exit() выходит из программы

they are the same when used in main() function

также return - инструкция, а exit() - это функция, для которой требуется файл заголовка stdlb.h