Должны ли мы использовать exit () в C?

80

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

Мне было интересно, как эта ситуация в C. Применимы ли аналогичные проблемы и в C? Я подумал, поскольку в C мы не используем конструкторы / деструкторы, в C ситуация может быть другой. Так можно ли использовать exitв C?

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

Гмониава
источник
2
«деструкторы объектов не будут вызываться» - это не совсем правильно (см .: cplusplus.com/reference/cstdlib/exit ). Вы думаете о quick_exit (см. Cplusplus.com/reference/cstdlib/quick_exit/?kw=quick_exit ).
Coder
У вас также могут быть некоторые проблемы, связанные с операционной системой, например, обычная роль SIGTERMсигнала в POSIX и Linux .... Ожидается, что хорошо настроенные серверы справятся с этим хорошо. И вам следует избегать использования SIGKILL(то есть попробовать kill -TERMтогда kill -QUITи только потом kill -KILLв качестве системного администратора)
Базиль Старынкевич
20
Как это не дубликат спустя почти 7 лет после запуска Stack Overflow?
Питер Мортенсен
7
@PeterMortensen: это будет удивительно, но я не знаю о хорошей альтернативе которой это дубли. Высокая оценка использования exit()функции неуместна (и удивляет большое количество голосов - это плохой вопрос). Почему мне не следует использовать функцию exit () в C? был бы хорошим кандидатом, если бы у него были ответы калибра этого вопроса. Это не так; обратное закрытие этого как дубликат уместно - и я сделал это.
Джонатан Леффлер,
Я не уверен, что вы хотите использовать printf в случае ошибки. Функция использует буферы, но, если у вас есть повреждение памяти, эти буферы могут быть плохими. Вы можете просто использовать 'write (2, "ERROR:", 7); написать (2, сообщение, strlen (сообщение));
Alex

Ответы:

82

Вместо abort()этого exit()функция в C считается "изящным" выходом.

Из C11 (N1570) 7.22.4.4/p2 Функция выхода (выделено мной):

exitФункция вызывает нормальное завершение программы произойти.

Стандарт также говорит в 7.22.4.4/p4, что:

Затем все открытые потоки с незаписанными буферизованными данными сбрасываются , все открытые потоки закрываются , а все файлы, созданные tmpfileфункцией, удаляются.

Также стоит посмотреть 7.21.3 / p5 Files :

Если mainфункция возвращается к своему исходному вызывающему объекту или если exit функция вызывается, все открытые файлы закрываются (следовательно, все выходные потоки сбрасываются) до завершения программы. Другие пути завершения программы, такие как вызов abortфункции, не требуют должного закрытия всех файлов.

Однако, как упоминается в комментариях ниже, вы не можете предполагать, что он будет охватывать все остальные ресурсы , поэтому вам может потребоваться прибегнуть atexit()и определить обратные вызовы для их выпуска индивидуально. На самом деле это именно то, что atexit()нужно делать, как сказано в 7.22.4.2/p2 Функция atexit :

В atexitрегистры функции функция , на которую указывает func, чтобы быть вызвана без аргументов при нормальном завершении программы.

Примечательно, что стандарт C не говорит точно, что должно происходить с объектами с выделенной продолжительностью хранения (т. Е. malloc()), Поэтому вам нужно знать, как это делается в конкретной реализации. Для современной ОС, ориентированной на хост, вполне вероятно, что система позаботится об этом, но все же вы можете решить это самостоятельно, чтобы отключить отладчики памяти, такие как Valgrind .

Гжегож Шпетковски
источник
3
Мне кажется, это правильный ответ. Соединение сокета закрывается при выходе, потому что завершение процесса закрывает все файловые дескрипторы независимо от того, были ли они открыты с помощью stdio, т.е. эквивалентно a close()на FD. Я не знаю, в каком смысле @BlueMoon считает это неправильным, но это не менее неверно, чем обращение к close(), и, если требуется дальнейшее прояснение, это именно то atexit(), для чего.
abligh
1
@abligh Современные ОС закрывают все файловые системы, связанные с этим процессом; но это что-то жестокое и нестандартное (о чем говорит Blue Moon).
edmz
3
@black, если требуется дополнительная очистка, atexit()можно использовать. Используя exit()себя не более жестоким , чем делать returnиз main().
abligh
5
Написание `` правильного '' программного обеспечения с использованием `` передовых методов '' - вот почему у меня так много приложений, которые не закрываются сразу, когда их об этом просят. :( Если ОС может что-то делать, вы должны позволить ей это сделать, а не возиться с пользовательским кодом пытается сделать что-то, что уже существует, уже протестировано и уже отлажено. Если ваша программная система не может выдержать внезапного, неожиданного выключения (например, из какого-то потока, вызывающего немедленное завершение внутри процесса, диспетчер задач 'Завершить процесс', 'убить -9 'или сбой питания), в любом случае он плохого качества.
Мартин Джеймс,
было бы хорошо, если бы вы могли добавить -если возможно- к своему ответу, какие ресурсы могут потребоваться для выпуска, в atexitпротивном случае это нормально. @BlueMoon: я не думаю, что использование такой функции dieозначает, что нельзя программировать, в некоторых случаях ее можно использовать. В противном случае вы также можете справиться с этим, используя только возвращаемые значения и т. Д.
gmoniava
23

Да, можно использовать exitв C.

Чтобы обеспечить все буферы и корректное завершение работы, рекомендуется использовать эту функцию atexit, подробнее об этом здесь.

Пример кода будет таким:

Теперь при каждом exitвызове функция cleanupбудет выполняться, что может обеспечить корректное завершение работы, очистку буферов, памяти и т. Д.

t0mm13b
источник
@IlmariKaronen Выделенная память не обрабатывается автоматически. Это ОС, которая может гарантировать, что память будет возвращена обратно в ОС. Аргумент Гжегожа Шпетковски опровергает ваше утверждение: примечательно, что стандарт C не говорит точно, что должно происходить с объектами с выделенной продолжительностью хранения (iemalloc ()), поэтому вам нужно знать, как это делается в конкретной реализации. Для современной ОС, ориентированной на хост, вполне вероятно, что система позаботится об этом, но все же вы можете решить это самостоятельно, чтобы отключить отладчики памяти, такие как Valgrind.
это
15

У вас нет конструкторов и деструкторов, но у вас могут быть ресурсы (например, файлы, потоки, сокеты), и важно их правильно закрыть. Буфер не может быть записан синхронно, поэтому выход из программы без правильного закрытия ресурса может привести к повреждению.

enrico.bacis
источник
8
Этот ответ - отличный пример того, что «не учите язык программирования; учитесь программировать». Обычно вы не можете избежать понимания проблемы, выбрав другой язык.
Kerrek SB
10
Я считаю, что это неверно. Если вы вызываете exit()(а не _exit()), atexitпроцедуры вызываются, и stdioбуферизация сбрасывается на диск. exit()именно там, чтобы позволить упорядоченный выход из программы.
abligh
2
@abligh Системы реального времени не освобождают заблокированную память при exit (), например, что приводит к утечкам. Другой пример - разделяемая память POSIX. Так что это зависит от среды, а не от строгого соблюдения стандарта C.
PP
2
@abligh: не все ресурсы предоставляются стандартной библиотекой. Хотя вы, безусловно, правы в том, что стандартная библиотека дает определенные гарантии, вам все же нужно подумать о глобальном потоке управления вашей программой и обязанностях каждой из ее частей, а также убедиться, что каждая отложенная ответственность обрабатывается надлежащим образом. Термин «ресурс» является кратким термином для этого общего понятия и выходит за рамки любой конкретной библиотеки, а управление ресурсами в конечном итоге является обязанностью программиста. Подобные услуги atexitмогут помочь, хотя они не всегда подходят.
Kerrek SB
2
@abligh: Вы, конечно, можете использовать, exitесли хотите, но это глобальный скачок в управлении, который сопровождается всеми недостатками неструктурированного управления, которые мы обсуждали. В качестве способа выхода из состояния отказа это может быть подходящим (и, похоже, это то, что хочет OP), хотя для нормального потока управления я, вероятно, предпочел бы разработать основной цикл с правильным условием выхода, чтобы вы действительно закончили возвращаясь из main.
Kerrek SB,
10

Использование exit()нормально

Два основных аспекта разработки кода, которые еще не упоминались, - это «многопоточность» и «библиотеки».

В однопоточной программе в коде, который вы пишете для реализации этой программы, можно использовать exit(). Мои программы используют его регулярно, когда что-то пошло не так, и код не собирается восстанавливаться.

Но…

Однако звонок exit()- это одностороннее действие, которое нельзя отменить. Вот почему и «потоки», и «библиотеки» требуют тщательного обдумывания.

Потоковые программы

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

Код библиотеки

И этот пункт о «осведомленности о структуре программы» применим и к коду в библиотеках. Вызывать библиотечную функцию общего назначения бывает очень редко exit(). Вы были бы справедливо расстроены, если бы одна из стандартных функций библиотеки C не вернулась только из-за ошибки. (Очевидно, что функции , как exit(), _Exit(), quick_exit(), abort()предназначены не для возвращения, то по - другому.) Функции в библиотеке C , следовательно , либо «не может не» или вернуть индикацию ошибки так или иначе. Если вы пишете код для использования в библиотеке общего назначения, вам необходимо тщательно продумать стратегию обработки ошибок для вашего кода. Он должен соответствовать стратегиям обработки ошибок программ, с которыми он предназначен для использования, или обработка ошибок может быть настраиваемой.

У меня есть ряд библиотечных функций (в пакете с заголовком "stderr.h", имя, которое ступает по тонкому льду), которые предназначены для выхода, поскольку они используются для сообщения об ошибках. Эти функции выходят по назначению. В том же пакете есть связанная серия функций, которые сообщают об ошибках и не завершаются. Существующие функции, конечно же, реализованы в терминах неоткрывающихся функций, но это внутренняя деталь реализации.

У меня есть много других библиотечных функций, и многие из них полагаются на "stderr.h"код для сообщения об ошибках. Это дизайнерское решение, которое я принял, и с ним я согласен. Но когда об ошибках сообщают функции, которые выходят, это ограничивает общую полезность кода библиотеки. Если код вызывает функции сообщения об ошибках, которые не завершаются, тогда основные пути кода в функции должны иметь дело с возвратами ошибок разумно - обнаруживать их и передавать сообщение об ошибке в вызывающий код.


Код моего пакета отчетов об ошибках доступен в моем репозитории SOQ (Stack Overflow Questions) на GitHub в виде файлов stderr.cи stderr.hв подкаталоге src / libsoq .

Джонатан Леффлер
источник
Это хорошо указано. Вы можете увидеть здесь пример библиотеки, которая вызывает, abort()когда необходимо выделить память либо, malloc()либо realloc(), представьте себе, что у вас есть приложение, которое связано со 100 библиотеками, и вам интересно, какая из них и как вызвала сбой вашего приложения. Более того, я не нашел упоминания abort()в их документации (но не поймите меня неправильно. Это отличная библиотека для своих целей).
Grzegorz Szpetkowski
@GrzegorzSzpetkowski И он даже не вызывает вручную flush после fprinting в stderr. Ой!
это
1
@this: этого не должно быть. Выходные данные stderrобычно буферизируются по строке. Если вывод заканчивается новой строкой, он все равно будет сброшен системой.
Джонатан Леффлер,
@JonathanLeffler Опора на пользователя, а не умный ход.
это
@this: Нет, полагаясь на реализацию, соответствующую требованиям стандарта C: §7.21.3 ¶7 При запуске программы предопределены три текстовых потока, которые не нужно открывать явно - стандартный ввод (для чтения обычного ввода), стандартный вывода (для записи обычного вывода) и стандартной ошибки (для записи диагностического вывода). В исходном состоянии стандартный поток ошибок не полностью буферизуется; … Требуется активное кодирование со стороны программиста, чтобы стандартная ошибка была полностью буферизована (и никому не поможет, если программист такой тупой - кроме «не использовать код»).
Джонатан Леффлер,
6

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

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

Или вы можете запустить его во встроенной системе. Там некуда выйти , чтобы , все это бежит в while(1)петле main(). Это может даже не быть определено в стандартной библиотеке.

pjc50
источник
2
И людям нельзя позволять владеть кухонными ножами, потому что кто-то может схватить их пригоршню и попытаться жонглировать ими или поиграть со своим ребенком в «мяч». Очевидно, я не согласен. Если кто-то решает скопировать мой код в свою программу, и мой код оказывается не подходящим для его целей, это его проблема, потому что он не может прочитать код, который он ассимилирует.
G-Man говорит: «Reinstate Monica»
3
Довольно грубая аналогия. C полон вещей, которые можно сделать, но, вероятно, следует избегать. Отсюда и существование IOCCC. Обратите внимание, что в моем сообщении не говорится «не следует».
pjc50
2

В зависимости от того, что вы делаете, выход может быть наиболее логичным выходом из программы на C. Я знаю, что это очень полезно для проверки правильности работы цепочек обратных вызовов. Возьмите этот пример обратного вызова, который я использовал недавно:

В основном цикле я настраиваю все для этой системы, а затем жду цикл while (1). Вместо этого можно сделать глобальный флаг для выхода из цикла while, но это просто и делает то, что нужно. Если вы имеете дело с любыми открытыми буферами, такими как файлы и устройства, вы должны очистить их перед закрытием для согласованности.

Дом
источник
-1

В большом проекте ужасно, когда может выйти любой код, кроме coredump. Трассировка очень важна для поддержки онлайн-сервера.

user4531555
источник