Чи слід використовувати 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 ).
Кодер,

Можливо, у вас також є деякі проблеми, пов'язані з операційною системою, наприклад, звичайна роль SIGTERMсигналу в POSIX та Linux .... Очікується, що добре виховані сервери з цим добре справляться. І вам слід уникати використання SIGKILL(тобто спробувати kill -TERMтоді kill -QUITі лише пізніше тоді kill -KILLяк сисадмін)
Базиль Старинкевич

20
Як це не дублікат майже через 7 років після запуску Stack Overflow?
Пітер Мортенсен,

7
@PeterMortensen: це буде дивно, але я не знаю про гарну альтернативу якій це дублі. Високо голосоване використання exit()функції не є загальним (і високий підрахунок голосів дивує - це невдале питання). Чому я не повинен використовувати функцію exit () у C? був би непоганим кандидатом, якби він мав відповіді рівня на це питання. Це не так; зворотне закриття цього як дублікат цього є доречним - і я це зробив.
Джонатан Леффлер

Я не впевнений, що ви хочете використовувати 'printf' у випадку помилки. Функція використовує буфери, але якщо у вас пошкоджена пам’ять, ці буфери можуть бути поганими. Ви можете просто використовувати 'write (2, "ПОМИЛКА:", 7); write (2, message, strlen (повідомлення));
Алекс

Відповіді:


82

Замість того abort(), то exit()функція в С вважається «витонченим» вихід.

З C11 (N1570) 7.22.4.4/p2 Функція виходу (наголос мій):

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

Стандарт також говорить у 7.22.4.4/p4, що:

Далі всі відкриті потоки з неписаними буферними даними очищаються , усі відкриті потоки закриваються , а всі файли, створені tmpfileфункцією, видаляються.

Також варто подивитися файли 7.21.3 / p5 :

Якщо 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 Modern OS закриє всі fds, пов'язані з цим процесом; але це щось брутальне і нестандартне (що говорить Синій Місяць).
edmz

3
@black, якщо потрібне подальше очищення, atexit()можна використовувати. Використання exit()самого себе не є більш жорстоким, ніж використання returnз main().
abligh

5
Написання `` належного '' програмного забезпечення з використанням `` хороших практик '' - ось чому у мене так багато програм, які не відразу ж закриваються, коли їх просять. :( Якщо ОС може щось зробити, вам слід дозволити це робити, замість того, щоб фартувати з кодом користувача намагаючись зробити щось, що вже існує, вже перевірено та вже налагоджено. Якщо ваша програмна система не витримує раптового, несподіваного вимкнення (наприклад, з якогось потоку, що вимагає негайного завершення в процесі, диспетчер завдань "Завершити процес", "вбити" -9 'або збій живлення), він і так низької якості.
Мартін Джеймс,

Буде чудово, якщо ви зможете додати -якщо це можливо до вашої відповіді, які саме ресурси вам потрібно буде звільнити atexit, інакше це нормально. @BlueMoon: Я не думаю, що використовуючи таку функцію, як dieозначає, що не можна програмувати, кілька разів, можливо, захочеться її використовувати. В іншому випадку, ви також можете впоратися з цим, використовуючи лише повернені значення тощо
gmoniava

23

Так, це нормально використовувати exitв C.

Для забезпечення всіх буферів та витонченого впорядкованого вимкнення рекомендується використовувати цю функцію atexit, більше інформації про це тут

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

Тепер, коли б exitне було викликано, функція cleanupбуде виконана, що може забезпечити витончене вимкнення, очищення буферів, пам'яті тощо.


@IlmariKaronen Виділена пам'ять не обробляється автоматично жодним чином. Саме ОС може переконатися, що пам’ять повертається до ОС. Аргумент zeжеґожа Шпетковського протистоїть вашому твердженню: примітно, стандарт С не говорить точно, що має статися з об’єктами, що мають визначену тривалість зберігання (iemalloc ()), вимагаючи від вас знання того, як це робиться при конкретній реалізації. Для сучасної ОС, орієнтованої на хост, цілком ймовірно, що система подбає про це, але все ж ви можете вирішити це самостійно, щоб заглушити налагоджувачі пам'яті, такі як Valgrind.
цього

15

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


8
Ця відповідь є чудовим прикладом "не вивчайте мову програмування; навчіться програмувати". Як правило, не можна уникнути розуміння проблеми, вибравши іншу мову.
Kerrek SB

10
Я вважаю, що це неправильно. Якщо ви викликаєте exit()(а не _exit()) atexit, викликаються підпрограми, а stdioбуферизація видаляється на диск. exit()саме там, щоб забезпечити упорядкований вихід із програми.
abligh

2
@abligh Системи реального часу не звільняють помилкову пам'ять при виході (), наприклад, що призводить до витоків. Ще один приклад - спільна пам’ять POSIX. Тож це залежить від навколишнього середовища, а не суворо дотримується стандарту C.
PP

2
@abligh: Не всі ресурси є стандартною бібліотекою. Хоча ви, безсумнівно, праві, що стандартна бібліотека дає певні гарантії, вам все одно потрібно подумати про глобальний потік контролю вашої програми та обов'язки кожної з її частин, а також забезпечити належну розгляд кожної відповідальності, що очікує. Термін "ресурс" є стислим терміном для цього загального поняття і перевершує будь-яку конкретну бібліотеку, а обробка ресурсів в кінцевому рахунку відповідальність програміста. Такі заклади atexitможуть допомогти, хоча вони не завжди є доречними.
Kerrek SB

2
@abligh: Звичайно, ти можеш використовувати, exitякщо хочеш, але це глобальний стрибок управління, який має усі негативні сторони неструктурованого контролю, про які ми вже говорили. Як спосіб завершення роботи з умови відмови він може бути доречним (і, схоже, це те, що хоче ОП), хоча для нормального потоку управління я б, напевно, віддав перевагу проектуванню головного циклу з належним умовою виходу, щоб ви справді опинились повертаючись з головного.
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()коли він Fais виділити пам'ять або malloc()або realloc(), уявіть, що у вас є додаток, що посилання з 100 бібліотеками та ви дивуєтеся , який один і як розбився додатки. Більше того, я не знайшов жодної згадки abort()в їх документації (але не помиляйтесь. Це чудова бібліотека за своїм призначенням).
Grzegorz Szpetkowski

@GrzegorzSzpetkowski І навіть вручну не викликає флеш після fprinting до stderr. Ой!
цього

1
@this: Цього не потрібно робити. Вихідні дані stderrзазвичай буферизуються рядком. Якщо вихід закінчується новим рядком, система все одно видалить.
Джонатан Леффлер,

@JonathanLeffler Спираючись на користувача, не розумний крок.
це

@this: Ні, покладаючись на реалізацію, яка дотримується вимог стандарту C: §7.21.3 ¶7 Під час запуску програми попередньо визначено три текстові потоки, які не потрібно відкривати явно - стандартний вхід (для читання звичайного введення), стандарт вихід (для запису звичайного виводу) та стандартна помилка (для запису діагностичного виводу). Спочатку відкритий, стандартний потік помилок не буферизований повністю; ... Це вимагає активного кодування програмістом, щоб зробити стандартну помилку повністю буферизованою (і нікому не допоможе, якщо програміст такий тупий - крім "не використовувати код").
Джонатан Леффлер

6

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

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

Або ви можете запустити його на вбудованій системі. Там нікуди вийти , щоб , все це біжить в while(1)петлі main(). Він може навіть не бути визначений у стандартній бібліотеці.


2
І людям не слід дозволяти володіти кухонними ножами, тому що хтось може схопити їх жменю і спробувати жонглювати ними, або пограти в «ловушку» зі своєю дитиною. Очевидно, я не згоден. Якщо хтось вирішить скопіювати мій код у свою програму, і мій код виявиться невідповідним його цілям, це його проблема, оскільки він не читає код, який він засвоює.
G-Man говорить "Відновити Моніку"

3
Досить сувора аналогія. У C повно речей, які можна зробити, але, мабуть, слід уникати. Звідси існування IOCCC. Зверніть увагу, що в моєму дописі не сказано "не слід".
pjc50,

2

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

У головному циклі я налаштував все для цієї системи, а потім зачекав (1). Замість цього можна зробити глобальний прапор для виходу з циклу while, але це просто і робить те, що йому потрібно зробити. Якщо ви маєте справу з будь-якими відкритими буферами, такими як файли та пристрої, вам слід очистити їх перед закриттям для постійності.


-1

У великому проекті страшно, коли будь-який код може вийти, крім coredump. Трасування дуже важливе для обслуговування Інтернет-сервера.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.