оператор возврата против выхода () в main ()

197

Должен ли я использовать exit()или просто returnзаявления в main()? Лично я одобряю returnутверждения, потому что я чувствую, что это похоже на чтение любой другой функции, и управление потоком, когда я читаю код, является плавным (по моему мнению). И даже если я хочу реорганизовать main()функцию, мне returnкажется, что это лучший выбор, чем exit().

Делает exit()что-то особенное, что returnнет?

Srikanth
источник

Ответы:

277

На самом деле, это различие, но это тонкое. Это имеет больше последствий для C ++, но различия важны.

Когда я звоню returnв main(), деструкторы будут вызываться для моих локально контекстных объектов. Если я позвоню exit(), никакой деструктор не будет вызван для моих локально ограниченных объектов! Перечитайте это. exit() не возвращается . Это означает, что, как только я это называю, «нет бэкси». Любые объекты, которые вы создали в этой функции, не будут уничтожены. Часто это не имеет значения, но иногда это имеет место, например закрытие файлов (конечно, вы хотите, чтобы все ваши данные были записаны на диск?).

Обратите внимание, что staticобъекты будут очищены, даже если вы позвоните exit(). Наконец, обратите внимание, что если вы используете abort(), ни один объект не будет уничтожен. То есть никакие глобальные объекты, никакие статические объекты и никакие локальные объекты не будут вызывать свои деструкторы.

Действуйте с осторожностью, предпочитая выход, а не возврат.

http://groups.google.com/group/gnu.gcc.help/msg/8348c50030cfd15a

Свободная память
источник
1
abort () завершается с ошибкой (ненулевой код выхода) и может быть даже основным. Если вам нужно выйти без вызова статических деструкторов, используйте _exit.
7
@Mike: есть разница между буферами файлов библиотеки C и объектами потоков файлов C ++. exit () - будучи частью библиотеки C - была разработана для координации и очистки первого, но может обойти второе: даже стандартный поток содержимого C ++ не записывается на диск (попробуйте - я сделал, он потерпит неудачу с / Linux / GCC), и, очевидно, пользовательские типы, которые буферизовали ввод / вывод, также не должны сбрасываться.
Тони Делрой
9
Примечание: утверждение: деструктор не будет вызываться для моих объектов с локальной областью видимости! больше не верно для C ++ 11: - Объекты, связанные с текущим потоком с продолжительностью хранения потока, уничтожаются (только C ++ 11). cplusplus.com/reference/cstdlib/exit
Илендир,
7
Значит, thread_localбудут называться деструкторы объектов. Деструкторы для других локальных объектов до сих пор не вызваны. ideone.com/Y6Dh3f
HolyBlackCat
3
Кстати, просто чтобы быть педантичным, и потому что этот ответ все еще может сбивать с толку читателей, использующих C: для C проблема с exit()чистым закрытием файлов на самом деле является неправильной. Единственное время, когда данные не могут быть сброшены, находится в противоположном случае: то есть, если вы используете returnfrom main()и один вызвал setbuf()или setvbuf()с буфером, объявленным как автоматическое хранение в main()(как обсуждено в ответе R. ниже). Очень плохо, что этот вопрос помечен как C, так и C ++ (и стиль кодирования - это не проблема стиля!).
Грег А. Вудс
25

Еще одно отличие: exitэто функция стандартной библиотеки, поэтому вам нужно включить заголовки и ссылку на стандартную библиотеку. Чтобы проиллюстрировать (на C ++), это допустимая программа:

int main() { return 0; }

но для использования exitвам понадобится включить:

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

Плюс к этому добавляется дополнительное предположение: у вызова exitfrom mainте же побочные эффекты, что и при возврате нуля. Как уже отмечали другие, это зависит от того, какой исполняемый файл вы создаете (то есть, кто вызывает main). Вы кодируете приложение, которое использует C-runtime? Плагин майя? Служба Windows? Водитель? Каждый случай потребует исследования, чтобы увидеть, если exitэквивалентно return. Использование IMHO, exitкогда вы действительно имеете в виду, return просто делает код более запутанным. OTOH, если вы действительно имеете в виду exit , то во что бы то ни стало используйте его.

jwfearn
источник
16

Существует по крайней мере одна причина для предпочтения exit: если какой-либо из ваших atexitобработчиков ссылается на данные продолжительности автоматического хранения mainили если вы использовали setvbufили setbufдля назначения одному из стандартных потоков буфер буферизации автоматического хранения main, то возвращение из mainпроизводит неопределенное поведение, но вызов exitдействителен.

Другое потенциальное использование (обычно зарезервированное для игрушечных программ) - выход из программы с рекурсивными вызовами main.

R .. GitHub ОСТАНОВИТЬ, ПОМОГАЯ ЛЕД
источник
1
@Seb ничего особенного в этом нет main()- это просто функция, как и любая другая. С другой стороны, поскольку он имеет особое упоминание в стандарте, стандарт должен быть достаточно осторожным в отношении того, как он определяет main()и что ему близко и дорого. Однако, в конце концов, хотя стандарт не требует (и не должен ) компиляторов делать что-то особенное с автоматическим хранением в main(). Пожалуйста, внимательно прочитайте сноску № 11 под абзацем, на который вы ссылались в своем комментарии.
Грег А. Вудс
1
@ GregA.Woods Интересно. Кажется, что есть какой-то нормативный текст, противоречащий информативному. В соответствии с директивами ИСО / МЭК нормативная ссылка считается "обязательной", когда информативное считается только дополнительным ... Кроме того, использование слова "воля" для передачи требования недопустимо; в соответствии с вышеупомянутым документом (Приложение H). Таким образом, информативный текст, безусловно, является недействительным.
аутист
2
@Seb: очевидно, что мы не намерены отменять требования к поведению автоматического хранения, и сноска была написана, чтобы прояснить это. Да, неточность, плохая формулировка в стандарте C. Любой, кто читал это, знает это. Мы также знаем, что комитет, как правило, не решает подобные проблемы, потому что намерение уже очевидно.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
1
@Seb: Это не спор или судебное дело, чтобы доказать, что ты прав. Цель должна состоять в том, чтобы получить четкое понимание того, что представляет собой реальный язык C (как задумано и реализовано), и выразить это в ответах, которые полезны для читателей. Нормативный текст является слегка неправильным (вопреки намерению того, что он должен был выразить) в том смысле, который по существу зафиксирован в сноске. Если вас это не устраивает, отправьте отчет о дефекте, но не ожидайте ответа. Вот как WG14 катится ...
R .. GitHub ОСТАНОВИТЬ, ПОМОГАЯ ЛЬДУ
3
@Seb: Вы, кажется, верите, что язык C можно понять, интерпретируя текст стандарта на естественном языке, как если бы он был абсолютно строгим. Это просто невозможно. Спецификация содержит ошибки, и WG14 не тратит свое время на переписывание материала, когда простая сноска поясняет, что они уже знают, что допустили ошибку, но что читатель может понять ее.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
5

Я всегда использую, returnпотому что стандартный прототип для main()говорит, что он возвращает int.

Тем не менее, некоторые версии стандартов предоставляют mainособый режим и предполагают, что он возвращает 0, если нет явного returnутверждения. Учитывая следующий код:

int foo() {}
int main(int argc, char *argv[]) {}

G ++ только генерирует предупреждение foo()и игнорирует пропущенный возврат из main:

% g++ -Wall -c foo.cc
foo.cc: In function int foo()’:
foo.cc:1: warning: control reaches end of non-void function
Альнитак
источник
Я не знаю о C, но стандарт C ++ указывает, что если вы не возвращаете значение в main, предполагается, что он возвращает 0.
Джейсон Бейкер,
Кажется, что C99 такой же: faq.cprogramming.com/cgi-bin/…
Джейсон Бейкер
2
C99 и C ++ возвращают 0, если нет оператора возврата, C90 нет.
d0k
То, что функция объявлена ​​как возвращаемое значение, не означает, что вы должны использовать ее returnдля завершения ее выполнения. Вызов exit()также является допустимым, а иногда и необходимым, способом прекращения выполнения любой функции. На самом деле, как я и другие описывали в другом месте, вызов exit()даже от main()передает гораздо более четкое намерение выйти из всего процесса, сохраняет автоматическое хранение до завершения процесса и облегчает обслуживание во время будущего рефакторинга кода. Таким образом, использование C returnв main()случае, когда целью является завершение процесса, возможно, является плохой практикой.
Грег А. Вудс
@ GregA.Woods это ваше мнение, но вряд ли заслуживает отрицательного голосования! То, что я написал выше, полностью соответствует стандарту , тогда как ваш аргумент - это просто семантика.
Альнитак
5

Я НАСТОЯТЕЛЬНО поддерживаю комментарий Р. об использовании exit (), чтобы избежать автоматического сохранения в памяти main()до того, как программа действительно завершится. return X;Заявление в main()не точно эквивалентно вызову exit(X);, так как динамическое хранение main()исчезает , когда main()возвращается, но он не обращается в нуль , если вызов exit()сделан вместо этого.

Кроме того, в C или любом C-подобном языке returnоператор настоятельно намекает читателю, что выполнение будет продолжаться в вызывающей функции, и хотя это продолжение выполнения обычно технически верно, если вы считаете подпрограмму запуска C, которая вызвала вашу main()функцию, это не так. именно то, что вы имеете в виду, когда вы хотите закончить процесс.

В конце концов, если вы хотите завершить свою программу из любой другой функции, кроме как main()вы должны вызвать exit(). Кроме того, последовательное выполнение этого main()делает ваш код намного более читабельным, а также значительно упрощает повторный анализ вашего кода; т. е. код, скопированный из main()какой-либо другой функции, не будет работать неправильно из-за случайных returnоператоров, которые должны были быть exit()вызовами.

Таким образом, объединяя все эти пункты вместе, можно сделать вывод, что это плохая привычка , по крайней мере для C, использовать returnоператор для завершения программы main().

Грег А. Вудс
источник
Вы можете найти 5.1.2.2.3p1 стандарта C интересным ...
аутист
Этот ответ заслуживает тщательного рассмотрения для программ на C, как это указано в ответе. Для использования с C ++ его необходимо тщательно взвесить с учетом всех ранее упомянутых предостережений. Для C ++ я бы предложил exit()вообще избегать , но используйте его, если альтернативы a throwили abort()не работают в определенном контексте. Но особенно избегайте exit()main и используйте return в main вместо обычной практики.
Эльджей,
5

Выполняет ли exit () что-то особенное, чего не делает return?

Некоторые компиляторы для необычных платформ exit()могут преобразовать свой аргумент в значение выхода вашей программы, в то время как возвращение из main()может просто передать значение непосредственно в среду хоста без какого-либо перевода.

Стандарт требует идентичного поведения в этих случаях (в частности, он говорит, что возвращение чего-либо, из- intсовместимого main()должно быть эквивалентно вызову exit()с этим значением). Проблема в том, что разные ОС имеют разные соглашения для интерпретации выходных значений. Во многих (МНОГО!) Системах 0 означает успех, а все остальное - неудача. Но, скажем, в VMS нечетные значения означают успех, а четные - неудачу. Если вы вернули 0 из main(), пользователь VMS увидит неприятное сообщение о нарушении доступа. На самом деле не было нарушения прав доступа - это было просто стандартное сообщение, связанное с кодом ошибки 0.

Затем появился ANSI и благословил, EXIT_SUCCESSи в EXIT_FAILUREкачестве аргументов вы могли бы передать exit(). Стандарт также говорит, что exit(0)должен вести себя идентично exit(EXIT_SUCCESS), так что большинство реализаций определяют EXIT_SUCCESSего 0.

Поэтому стандарт ставит вас в тупик в VMS, поскольку не оставляет стандартного способа вернуть код ошибки, который имеет значение 0.

Поэтому компилятор VAX / VMS C начала 1990-х годов не интерпретировал возвращаемое значение main(), а просто возвращал любое значение в среду хоста. Но если бы вы использовали exit()его, он сделал бы то, что требовал стандарт: преобразовал EXIT_SUCCESS(или 0) в код успеха и EXIT_FAILUREв общий код ошибки. Чтобы использовать EXIT_SUCCESS, вы должны были передать его exit(), вы не могли вернуть его main(). Я не знаю, сохранили ли такое поведение более современные версии этого компилятора.

Портативная программа на Си раньше выглядела так:

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

int main() {
  printf("Hello, World!\n");
  exit(EXIT_SUCCESS);  /* to get good return value to OS */
  /*NOTREACHED*/ /* to silence lint warning */
  return 0;  /* to silence compiler warning */
}

В стороне: Если я правильно помню, соглашение VMS для значений выхода более нюанс, чем нечетное / четное. На самом деле он использует что-то вроде младших трех битов для кодирования уровня серьезности. В целом, однако, уровни нечетной серьезности указывают на успех или различную информацию, а четные указывают на ошибки.

Адриан Маккарти
источник
Некоторые старые компиляторы предварительно ANSI может пролечили значение returnedпо - mainразному от значения , переданного exit- но стандарт в частности , говорится : «Если тип возвращаемого значения mainфункции типа совместимы с int, возвращение из первоначального вызова в mainфункции эквивалентно вызову exitфункции со значением, возвращаемым mainфункцией в качестве аргумента ". Это С11; C89 / C90 была почти такая же формулировка.
Кит Томпсон
На самом деле. Тем не менее, некоторые компиляторы эпохи ANSI не получили этого права и требовали явного использования exit для получения правильного возвращаемого значения, возвращаемого в хост-среду. Поскольку стандарт (даже тогда) требует, чтобы 0 обрабатывался так же, как EXIT_SUCCESSи не было никакого способа вернуть специфичный для платформы статус ошибки со значением 0, возможно, поэтому некоторые из компиляторов эпохи рассматривали возврат из основного и по exit()разному.
Адриан Маккарти
У вас есть цитата для этого? Отдельная проблема заключается в том, есть ли у каких-либо текущих компиляторов именно эта ошибка. Ваш ответ для фразы в настоящем времени.
Кит Томпсон,
Это справедливая критика. Я изменил формулировку, чтобы ограничить область применения конкретным случаем, о котором я знаю.
Адриан Маккарти
0

В С возврат из mainточно такой же, как вызов exitс тем же значением.

Раздел 5.1.2.2.3 стандарта C гласит:

Если возвращаемый тип главной функции является типом, совместимым с int, возврат из начального вызова основной функции эквивалентен вызову функции выхода со значением, возвращаемым главной функцией в качестве аргумента ; 11) при достижении}, завершающего функцию main, возвращается значение 0. Если возвращаемый тип не совместим с int, состояние завершения, возвращаемое в хост-среду, не указывается.

Правила для C ++ немного отличаются, как упомянуто в других ответах.

dbush
источник
-1

На самом деле есть разница между exit(0)и return(0)в main- когда ваша mainфункция вызывается несколько раз.

Следующая программа

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

int main(int argc, char** argv) {
  if (argc == 0)
    return(0);
  printf("%d", main(argc - 1, argv));
}

Беги как

./program 0 0 0 0

Результатом будет следующий вывод:

00000

Однако этот:

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

int main(int argc, char** argv) {
  if (argc == 0)
    exit(0);
  printf("%d", main(argc - 1, argv));
}

Не буду ничего печатать независимо от аргументов.

Если вы уверены, что никто никогда не позвонит вам mainявно, технически это не будет большой разницей в целом, но поддержание более ясного кода exitбудет выглядеть намного лучше. Если вы по какой-то причине хотите позвонить main- вам следует настроить его под свои нужды.

Говоря о С.

radrow
источник