Як закінчити код C ++


267

Я хотів би, щоб мій код C ++ припинив роботу, якщо виконано певну умову, але я не знаю, як це зробити. Тож у будь-який момент, якщо ifтвердження є істинним, скасуйте такий код:

if (x==1)
{
    kill code;
}

98
Використовуйте належну логіку. При main()використанні return, у функціях використовують належне значення повернення або кидають належне Виняток. Не використовуйте exit()!
Кей - SE

6
@JonathanLeffler: Коли компілюється з NDEBUGвизначеними, assertвони, ймовірно, стануть неоперативними, тому вам не слід покладатися на нього більше, ніж виявляти помилки.
MvG

13
@jamesqf: a returnз main()цілком логічно. Він встановлює код виходу, про який повідомляє програма, операційній системі, що може бути корисно у багатьох випадках (наприклад, якщо вашу програму можна використовувати як частину більшого процесу).
ААТ

11
Цікаво, що цей Q є дублікатом цього з 5 років тому, чий прийнятий відповідь зовсім інший: stackoverflow.com/questions/1116493/how-to-quit-ac-program Я здогадуюсь, що громаді знадобилося 5 років, щоб навчитися цьому сліпо виклик std :: вихід може бути поганим?
Брандін

5
Запитання Чому використання exit()вважається поганим? - ТАК 25141737 і як вийти з програми C ++? - SO 1116493 зараз закрито як копії цього. Перший із них має посилання на "Програмне забезпечення, що відповідає лише збоям", яке, можливо, варто переглянути, якщо тільки стимулювати ваше роздуми про те, як написати надійне програмне забезпечення.
Джонатан Леффлер

Відповіді:


431

Існує декілька способів, але спочатку потрібно зрозуміти, чому очищення об'єкта важливо, а отже, і причина std::exit маргіналізована серед програмістів на C ++.

RAII та розмотування стека

C ++ використовує ідіому під назвою RAII , що, простіше кажучи, означає, що об'єкти повинні виконувати ініціалізацію в конструкторі та очищення в деструкторі. Наприклад,std::ofstream клас [може] відкрити файл під час конструктора, потім користувач виконує вихідні операції над ним, і, нарешті, наприкінці свого життєвого циклу, як правило, визначається його обсягом, називається деструктор, який по суті закриває файл і видаляє будь-який записаний вміст на диск.

Що станеться, якщо ви не потрапите до деструктора, щоб промити та закрити файл? Хто знає! Але, можливо, він не запише всі дані, які він повинен був записати у файл.

Наприклад, розгляньте цей код

#include <fstream>
#include <exception>
#include <memory>

void inner_mad()
{
    throw std::exception();
}

void mad()
{
    auto ptr = std::make_unique<int>();
    inner_mad();
}

int main()
{
    std::ofstream os("file.txt");
    os << "Content!!!";

    int possibility = /* either 1, 2, 3 or 4 */;

    if(possibility == 1)
        return 0;
    else if(possibility == 2)
        throw std::exception();
    else if(possibility == 3)
        mad();
    else if(possibility == 4)
        exit(0);
}

Що відбувається в кожній можливості:

  • Можливість 1: Повернення по суті залишає поточну область функцій, тому вона знає про закінчення життєвого циклу, osтаким чином викликаючи свого деструктора і виконуючи належну очистку, закриваючи та перемикаючи файл на диск.
  • Можливість 2: Закидання винятку також піклується про життєвий цикл об'єктів у поточному масштабі, таким чином роблячи належну очистку ...
  • Можливість 3: Тут розмотування стека вступає в дію! Навіть незважаючи на те, що виняток закинуто inner_mad, розмотувач піде через стек madі mainвиконати належну очистку, всі об'єкти будуть зруйновані належним чином, включаючи ptrі os.
  • Можливість 4: Ну, тут? exitє функцією C, і це не відомо і не сумісне з ідіомами C ++. Він не виконує очищення ваших об'єктів, в тому числі osв тій же самій області. Таким чином, ваш файл не буде закритий належним чином, і тому вміст ніколи не може бути записаний у нього!
  • Інші можливості: Це просто залишить основну сферу дії, виконуючи неявну return 0і тим самим надаючи такий же ефект, як і можливість 1, тобто правильне очищення.

Але не будьте настільки впевнені у тому, що я вам щойно сказав (переважно можливості 2 та 3); продовжуйте читати, і ми дізнаємось, як виконати правильне очищення на основі виключень.

Можливі шляхи до кінця

Повернення з головного!

Ви повинні робити це по можливості; завжди віддайте перевагу поверненню зі своєї програми, повертаючи належний статус виходу з основного.

Абонент вашої програми, а можливо, і операційна система, можливо, захоче знати, успішно чи ні було виконано те, що ваша програма повинна зробити. З цієї ж причини вам слід повернути або нуль, або EXIT_SUCCESSсигналізувати про те, що програма успішно завершилась, і EXIT_FAILUREсигнал про те, що програма завершилася невдало, будь-яка інша форма повернутого значення визначається реалізацією ( §18.5 / 8 ).

Однак ви можете бути дуже глибоко в стеці викликів, і повернення всього цього може бути болісним ...

[Не] кидайте виняток

Викидання винятку виконає належну очистку об'єкта за допомогою розмотування стека, викликавши деструктор кожного об'єкта в будь-якому попередньому діапазоні.

Але ось улов ! Це визначено реалізацією, чи виконується розмотування стека, коли викинутий виняток не обробляється (за допомогою пункту catch (...)) або навіть якщо у вас є noexceptфункція посеред стека виклику. Про це йдеться у § 15.5.1 [крім.закінчити] :

  1. У деяких ситуаціях обробку винятків потрібно відмовитися від менш тонких методів обробки помилок. [Примітка. Це такі ситуації:

    [...]

    - коли механізм обробки винятків не може знайти обробник для викинутого винятку (15.3), або коли пошук обробника (15.3) стикається із самим зовнішнім блоком функції з noexcept-специфікою , яка не дозволяє виключення (15.4), або [...]

    [...]

  2. У таких випадках викликається std :: endinate () (18.8.3). У ситуації, коли не знайдено відповідного обробника, визначається реалізація того, чи слід стек розкручується, перш ніж std :: terminate () викликається [...]

Тож ми мусимо це зловити!

Викиньте виняток і ловіть його головним чином!

Оскільки невиконані винятки можуть не виконувати розмотування стека (і, отже, не виконувати належну очистку) , ми повинні зафіксувати виняток в основному, а потім повернути статус виходу ( EXIT_SUCCESSабо EXIT_FAILURE).

Таким чином, можлива хороша настройка:

int main()
{
    /* ... */
    try
    {
        // Insert code that will return by throwing a exception.
    }
    catch(const std::exception&)  // Consider using a custom exception type for intentional
    {                             // throws. A good idea might be a `return_exception`.
        return EXIT_FAILURE;
    }
    /* ... */
}

[Не] std :: вихід

Це не виконує будь-якого типу розмотування стека, і жоден живий об'єкт у стеку не закличе відповідного деструктора для проведення очищення.

Це застосовується в §3.6.1 / 4 [basic.start.init] :

Припинення програми без виходу з поточного блоку (наприклад, викликом функції std :: exit (int) (18.5)) не знищує жодних об'єктів з автоматичною тривалістю зберігання (12.4) . Якщо std :: exit викликається для завершення програми під час знищення об'єкта зі статичною тривалістю або тривалості зберігання, програма має невизначене поведінку.

Подумайте над цим зараз, чому б ви робили таке? Скільки предметів ви болісно пошкодили?

Інші [як погані] альтернативи

Є й інші способи припинення програми (крім збоїв) , але вони не рекомендуються. Просто задля уточнення вони будуть представлені тут. Зверніть увагу, як звичайне завершення програми не означає розмотування стека, а нормальний стан для операційної системи.

  • std::_Exit викликає нормальне припинення програми, і все.
  • std::quick_exitвикликає нормальне припинення програми та std::at_quick_exitобробляє дзвінки , інша очистка не проводиться.
  • std::exitвикликає нормальне припинення програми, а потім викликає std::atexitобробників. Інші види очищення виконуються, наприклад, виклик статичних об'єктів деструкторів.
  • std::abortвикликає ненормальне припинення програми, очищення не проводиться. Це слід назвати, якщо програма припиняється дійсно, дійсно несподівано. Це нічого, крім сигналу ОС про аномальне припинення. Деякі системи виконують дамп ядра в цьому випадку.
  • std::terminateвиклики, std::terminate_handlerякі дзвінки std::abortза замовчуванням.

25
Це досить інформативно: зокрема, я ніколи не розумів, що викидання виключення, яке ніде не обробляється (наприклад: деякі newкидки, std::bad_allocі ваша програма забула зловити цей виняток де завгодно), не буде належним чином розкручувати стек перед завершенням. Це здається нерозумно для мене: це було б легко по суті обернути виклик mainв тривіальної try{- }catch(...){}блок , який забезпечував би стек розмотувати правильно робиться в таких випадках, без яких - або витрат (я маю в виду: програми не використовувати це буде платити не штраф). Чи є якась конкретна причина, що це не робиться?
Марк ван Левен

12
@MarcvanLeeuwen Однією з можливих причин є налагодження: ви хочете прорватися до налагоджувача, як тільки буде передано необроблений виняток. Розмотування стека та очищення видалить контекст того, що спричинило збій, який ви хочете налагодити. Якщо налагоджувача немає, може бути краще скинути ядро, щоб можна було виконати постмортетний аналіз.
Х'ю Аллен

11
Іноді мій браузер вимикається (я звинувачую спалах) і споживає кілька гігабайт оперативної пам’яті, а моя ОС скидає сторінки на жорсткий диск, роблячи все повільно. Коли я закриваю браузер, він виконує належну розмотування стека, тобто всі ці гігабайти оперативної пам’яті зчитуються з жорсткого диска та копіюються в пам’ять просто для звільнення, що займає хвилину або близько того. Мені б хотілося, щоб вони використовували std::abortнатомість, щоб ОС могла звільнити всю пам'ять, сокети та дескриптори файлів, не міняючи ні хвилини.
nwp

2
@ nwp, я розумію почуття. Але миттєве вбивство може пошкодити файли, а не зберегти мої останні вкладки тощо:
Пол Дрейпер

2
@PaulDraper я, безумовно, не вважав би це допустимим, якби браузер не зміг відновити вкладки, які я відкрив до втрати електроенергії. Звичайно, якби я щойно відкрив вкладку, і ще не встиг її зберегти, вона буде втрачена. Але крім цього, я кажу, що немає виправдання для його втрати.
kasperd

61

Як зазначив Мартін Йорк, вихід не виконує необхідного очищення, як це робить повернення.

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

Розглянемо нижченаведений приклад. За допомогою наступної програми буде створено файл із зазначеним змістом. Але якщо повернення прокоментується і не коментується вихід (0), компілятор не запевняє вас, що у файлі буде потрібний текст.

int main()
{
    ofstream os("out.txt");
    os << "Hello, Can you see me!\n";
    return(0);
    //exit(0);
}

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


2
Що ви рекомендуєте досягти такої поведінки у трохи більшій програмі? Як ви завжди повертаєтесь до основної чистоти, якщо десь глибше в коді спрацьовує помилка, яка повинна вийти з програми?
Януш

5
@Janusz, У такому випадку ви можете використовувати / викидати винятки, якщо не повернути заздалегідь визначене значення, тобто мати значення повернення від функції, як, наприклад, return 0 при успіху, 1 у разі відмови, але продовжуйте виконання , -1 у разі відмови та виходу з програми. Виходячи з поверненого значення функції, якщо це невдача, просто поверніться від основного після виконання будь-яких ще активних операцій очищення. Нарешті, використовуйте вихід розумно, я не хочу цього уникати.
Нарендра N

1
@NarendraN, "необхідне очищення" є невиразним - ОС подбає (Windows / Linux) про правильне звільнення пам'яті та файлів. Щодо виводу "відсутнього" файлу. Якщо ви наполягаєте на тому, що це може бути справжньою проблемою, див. Stackoverflow.com/questions/14105650/how-does-stdflush-work Якщо у вас помилка, правильний журнал повідомляє, що ваша програма досягнуто невизначеного стану, і ви можете встановити точку перерви прямо перед точкою реєстрації. Як це робить налагодження складніше?
Маркус

2
Зауважте, що цю відповідь було об'єднано з питання Як вийти з програми C ++? - SO 1116493 . Ця відповідь була написана приблизно за 6 років до того, як було задано це питання.
Джонатан Леффлер

для сучасного використання C ++ return (EXIT_SUCCESS);замість цьогоreturn(0)
Jonas Stein

39

Викличте std::exitфункцію.   


2
Які деструктори об'єктів викликаються, коли ви викликаєте цю функцію?
Роб Кеннеді

37
exit () не повертається. Тож жодне розмотування стека не може відбутися. Навіть глобальні об’єкти не руйнуються. Але функції, зареєстровані atexit (), будуть називатися.
Мартін Йорк

16
Будь ласка, не телефонуйте, exit()коли ваш бібліотечний код працює в моєму хост-процесі - останній вийде посеред нізвідки.
гострий зуб

5
Зауважте, що цю відповідь було об'єднано з питання Як вийти з програми C ++? - SO 1116493 . Ця відповідь була написана приблизно за 6 років до того, як було задано це питання.
Джонатан Леффлер

23

Люди кажуть "вихід дзвінка (код повернення)", але це погана форма. У невеликих програмах це добре, але з цим існує низка питань:

  1. У вас з’явиться кілька точок виходу з програми
  2. Це робить код більш складним (наприклад, використання goto)
  3. Він не може випустити пам'ять, виділену під час виконання

Дійсно, єдиний раз, коли ви повинні вийти з проблеми, це цей рядок у main.cpp:

return 0;

Якщо ви використовуєте exit () для обробки помилок, вам слід дізнатися про винятки (і вкладення винятків), як про набагато більш елегантний і безпечний метод.


9
У багатопотоковому середовищі виняток, викинутий в інший потік, не буде оброблятися, хоча main () - потрібне певне ручне перехресне спілкування до закінчення терміну дії підпорядкованого потоку.
Стів Гілхам

3
1. і 2. залежать від програміста, при правильному реєстрації це не проблема, оскільки виконання припиняється назавжди. Що стосується 3: Це просто неправильно, ОС звільнить пам'ять - можливо, виключаючи вбудовані пристрої / в реальному часі, але якщо ви це робите, ви, ймовірно, знаєте свої речі.
Маркус

1
Зауважте, що цю відповідь було об'єднано з питання Як вийти з програми C ++? - SO 1116493 . Ця відповідь була написана приблизно за 6 років до того, як було задано це питання.
Джонатан Леффлер

1
Якщо виникла помилка, ви не повинні повертати 0. Ви повинні повернути 1 (або, можливо, якесь інше значення, але 1 - це завжди безпечний вибір).
Кеф Шектер

14

return 0;покладіть це куди завгодно, int main()і програма негайно закриється.


нічний німець, @ evan-carslake та всі інші, я також додам, що це питання № 36707 дає дискусію щодо того, чи повинен випадок повернення просто виникати в будь-якій рутині. Це рішення; однак, залежно від конкретної ситуації, я б не сказав, що це найкраще рішення.
localhost

1
ОП про це нічого не говорив, тому я вважаю, що досить неправдиво вважати, що код мав на увазі функцію main.
Марк ван Левен

@localhost Повідомлення про повернення є абсолютно необов’язковим у будь-якій ситуації та за будь-яких умов. Однак, int main()різне. Програма використовує int main()для початку та закінчення. Іншого способу це зробити не існує. Програма буде працювати до тих пір, поки ви не повернете справжнє або хибне. Майже весь час ви повернете 1 або true, щоб вказати, що програма правильно закрилася (напр .: може використовувати false, якщо ви не змогли звільнити пам'ять з будь-якої причини.) Дозволити програму запустити себе завжди - це погана ідея, наприклад:int main() { int x = 2; int foo = x*5; std::cout << "blah"; }
Еван Карслайк

@EvanCarslake Зрозумів, якщо ви помітили коментар, який я розмістив в іншому місці щодо цього питання, я з цим знайомий int main; однак, не завжди гарна ідея, що в основній процедурі є кілька заявок на повернення. Один із потенційних застережень - поліпшення читабельності коду; однак, використовуючи щось на зразок булевого прапора, який змінює стан, щоб запобігти запусканню певних розділів коду у цьому додатку. Особисто я вважаю, що загальна заява про повернення робить більшість програм більш читабельними. Нарешті, питання щодо очищення пам'яті та належного закриття об'єктів вводу / виводу перед виходом.
localhost

2
@EvanCarslake ви не повернете trueвід mainвказати правильне закінчення. Ви повинні повернути нуль або EXIT_SUCCESS(або, якщо хочете false, який би неявно перетворився на нуль), щоб вказати нормальне припинення. Щоб вказати на невдачу, ви можете повернутися EXIT_FAILURE. Будь-яке інше значення коду визначено реалізацією (для систем POSIX це означатиме фактичний код помилки).
Руслан

11

Програма припиняється, коли потік виконання досягне кінця основної функції.

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


2
Зауважте, що цю відповідь було об'єднано з питання Як вийти з програми C ++? - SO 1116493 . Ця відповідь була написана приблизно за 6 років до того, як було поставлено це запитання.
Джонатан Леффлер

11

Або поверніть значення зі свого mainабо скористайтеся exitфункцією. Обидва беруть інт. Насправді не важливо, яке значення ви повертаєте, якщо у вас немає зовнішнього процесу, який слідкує за значенням повернення.


3
Зауважте, що цю відповідь було об'єднано з питання Як вийти з програми C ++? - SO 1116493 . Ця відповідь була написана приблизно за 6 років до того, як було поставлено це запитання.
Джонатан Леффлер

11

Якщо у вас є помилка десь у коді, то або киньте виняток, або встановіть код помилки. Завжди краще кинути виняток, а не встановлювати коди помилок.


2
Зауважте, що цю відповідь було об'єднано з питання Як вийти з програми C ++? - SO 1116493 . Ця відповідь була написана приблизно за 6 років до того, як було поставлено це запитання.
Джонатан Леффлер

9

Як правило, ви використовуєте exit()метод з відповідним статусом виходу .

Нуль означатиме успішний біг. Ненульовий статус вказує на певну проблему. Цей вихідний код використовується батьківськими процесами (наприклад, скриптами оболонки), щоб визначити, чи успішно працює процес.


1
Використання виходу МОЖЕ означає, що у вас є проблеми з дизайном. Якщо програма спрацьовує правильно, вона просто припиняється, коли вона є основною return 0;. Я припускаю, що exit()це подобається, assert(false);і тому його слід використовувати лише для розробки для раннього виникнення проблем з ловом.
CoffeDeveloper

2
Зауважте, що цю відповідь було об'єднано з питання Як вийти з програми C ++? - SO 1116493 . Ця відповідь була написана приблизно за 6 років до того, як було поставлено це запитання.
Джонатан Леффлер

7

Крім виклику exit (error_code) - який викликає обробники atexit, але не деструктори RAII тощо - я все більше і більше використовую винятки.

Все більше і більше виглядає моя основна програма

int main(int argc, char** argv) 
{
    try {
        exit( secondary_main(argc, argv );
    }
    catch(...) {
        // optionally, print something like "unexpected or unknown exception caught by main"
        exit(1);
    }
}

де sekundar_main, де розміщені всі спочатку речі, тобто первинна головна назва перейменована на Second_main, а додається головна заглушка вище. Це просто вигадка, так що між лотком і головним ловом не буде занадто багато коду.

Якщо ви хочете, ловіть інші типи винятків.
Мені дуже подобається ловити типи помилок рядків, як-от std :: string або char *, і друкувати ті, хто знаходиться в оброблювачі вилову в основному.

Використання таких винятків принаймні дозволяє викликати деструктори RAII, щоб вони могли робити очищення. Що може бути приємним і корисним.

Загалом, поводження з помилками C - вихід та сигнали - та поводження з помилками C ++ - випробування / вилов / викидання - грають разом у кращому випадку непослідовно.

Потім, де ви виявите помилку

throw "error message"

або якийсь більш конкретний тип виключення.


До речі: я добре знаю, що використання такого типу даних, як рядок, який може включати динамічне розподілення пам’яті, не є хорошою ідеєю в обробці винятків або навколо нього для виключень, які можуть бути пов’язані з втратою пам’яті. Строкові константи в стилі C не є проблемою.
Krazy Glew

1
Немає сенсу телефонувати exitу вашу програму. Оскільки ти в main, ти можеш просто return exitCode;.
Руслан

-1

Якщо ваш випадок, якщо заявка знаходиться в циклі, ви можете використовувати

 break; 

Якщо ви хочете уникнути деякого коду та продовжуйте циклічно, використовуйте:

продовжувати;

Якщо ваша заява if не знаходиться в циклі, ви можете використовувати:

 return 0;

Or 




  exit();

-2

Чувак ... exit()функція визначається під stdlib.h

Тому потрібно додати препроцесора.

Помістіть include stdlib.hу розділі заголовка

Потім використовуйте exit();куди завгодно, але не забудьте поставити міжрежик у дужках виходу.

наприклад:

exit(0);

-2

Якщо стан, на який я тестую, є справді поганою новиною, я роблю це:

*(int*) NULL= 0;

Це дає мені приємний корредум, звідки я можу вивчити ситуацію.


4
Це цілком можливо оптимізувати, оскільки це спричиняє не визначену поведінку. Фактично, вся гілка виконання, що має таку заяву, може бути знищена компілятором.
Руслан

-2

Щоб розбити умову, використовуйте return (0);

Отже, у вашому випадку це було б:

    if(x==1)
    {
        return 0;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.