Как завершить код C ++


267

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

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

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

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

13
@jamesqf: returnот main()вполне логично. Он устанавливает код завершения, сообщаемый программой для операционной системы, что может быть полезно во многих случаях (например, если ваша программа может использоваться как часть более крупного процесса).
AAT

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

5
Вопросы Почему использование exit()считается плохим? - SO 25141737 и как выйти из программы на C ++? - SO 1116493 теперь закрыты как дубликаты этого. Первая из них имеет ссылку на «Crash-only Software», на которую стоит взглянуть, хотя бы для того, чтобы стимулировать ваши мысли о том, как писать надежное программное обеспечение.
Джонатан Леффлер

Ответы:


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: Return, по существу, оставляет текущую область действия функции, поэтому он знает о конце жизненного цикла 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 [except.terminate] :

  1. В некоторых ситуациях обработка исключений должна быть прекращена для менее тонких методов обработки ошибок. [Примечание: эти ситуации:

    [...]

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

    [...]

  2. В таких случаях вызывается std :: terminate () (18.8.3). В ситуации, когда соответствующий обработчик не найден, определяется реализацией, будет ли стек разматываться перед вызовом std :: terminate () [...]

Таким образом, мы должны поймать это!

Сделайте исключение и поймайте его на главном!

Поскольку неперехваченные исключения могут не выполнять разматывание стека (и, следовательно, не будут выполнять надлежащую очистку) , мы должны перехватить исключение в main и затем вернуть состояние выхода ( 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 Я бы, конечно, не счел это допустимым, если бы браузер не мог восстановить вкладки, которые я открыл до потери питания. Конечно, если бы я только что открыл вкладку, и у нее еще не было времени ее сохранить, она была бы потеряна. Но, кроме этого, я говорю, что нет оправдания потерять его.
Касперд

61

Как отметил Мартин Йорк, exit не выполняет необходимую очистку, как return.

Всегда лучше использовать возврат в месте выхода. В случае, если вы не в основном, где бы вы хотели выйти из программы, сначала вернитесь в главное.

Рассмотрим приведенный ниже пример. С помощью следующей программы будет создан файл с указанным содержимым. Но если return является закомментированным и некомментированным exit (0), компилятор не гарантирует, что в файле будет нужный текст.

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

Не только это: наличие нескольких точек выхода в программе усложнит отладку. Используйте выход только тогда, когда это может быть оправдано.


2
Что вы рекомендуете для достижения такого поведения в немного большей программе? Как вы всегда возвращаетесь к main чисто, если где-то в глубине кода возникает условие ошибки, которое должно выйти из программы?
Януш

5
@Janusz, в этом случае вы можете использовать / throw исключения, если не вернуть предопределенное значение, т.е. иметь возвращаемое значение из функции, например, вернуть 0 в случае успеха, 1 в случае сбоя, но продолжить выполнение , -1 в случае сбоя и выхода из программы. На основе возвращаемого значения из функции, если это сбой, просто вернитесь из основного после выполнения каких-либо дополнительных действий по очистке. Наконец, используйте выход разумно, я не хочу избегать его.
Нарендра N

1
@NarendraN, «необходимая очистка» является расплывчатой ​​- ОС позаботится (Windows / Linux), чтобы дескрипторы памяти и файлов были правильно освобождены. Что касается вывода «отсутствующего» файла: если вы настаиваете на том, что это может быть реальной проблемой, см. Stackoverflow.com/questions/14105650/how-does-stdflush-work Если у вас есть условие ошибки, правильное ведение журнала говорит вам, что ваша программа достиг неопределенного состояния, и вы можете установить точку останова прямо перед вашей точкой входа. Как это усложняет отладку?
Маркус

2
Обратите внимание, что этот ответ был объединен с вопросом Как выйти из программы на C ++? - SO 1116493 . Этот ответ был написан примерно за 6 лет до того, как был задан этот вопрос.
Джонатан Леффлер

return (EXIT_SUCCESS);вместо современного C ++ return(0)
Jonas Stein

39

Вызовите std::exitфункцию.   


2
Какие деструкторы объектов вызываются при вызове этой функции?
Роб Кеннеди

37
exit () не возвращается. Таким образом, разматывание стека не может быть выполнено. Даже глобальные объекты не разрушаются. Но функции, зарегистрированные с помощью atexit (), будут вызваны.
Мартин Йорк

16
Пожалуйста, не звоните, exit()когда ваш библиотечный код выполняется внутри моего хост-процесса - последний выйдет из ниоткуда.
Sharptooth

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()и программа сразу закроется.


the-nightman, @ evan-carslake и все остальные, я бы также добавил, что в этом вопросе # 36707 обсуждается вопрос о том, должно ли выражение return только что произойти где-либо в подпрограмме. Это решение; однако, в зависимости от конкретной ситуации, я бы не сказал, что это лучшее решение.
localhost

1
OP ничего не сказал по этому поводу, поэтому я считаю довольно опрометчивым предполагать, что код должен быть внутри функции main.
Марк ван Леувен

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

@EvanCarslake Понятно, если вы заметили комментарий, который я разместил в другом месте по этому вопросу, я знаю об этом int main; однако, не всегда хорошая идея иметь несколько операторов return в основной процедуре. Одно потенциальное предостережение - улучшить читаемость кода; однако, используя что-то вроде логического флага, который изменяет состояние, чтобы предотвратить выполнение определенных участков кода в этом состоянии приложения. Лично я считаю, что общее выражение return делает большинство приложений более читабельными. И наконец, вопросы об очистке памяти и правильном закрытии объектов ввода-вывода перед выходом.
localhost

2
@EvanCarslake вы не вернете trueот mainуказать правильное окончание. Вы должны вернуть ноль или EXIT_SUCCESS(или, если хотите, falseкоторый будет неявно преобразован в ноль), чтобы указать нормальное завершение. Чтобы указать сбой, вы можете вернуться EXIT_FAILURE. Любое другое значение кода определяется реализацией (в системах POSIX это будет означать фактический код ошибки).
Руслан

11

Программа завершится, когда поток выполнения достигнет конца основной функции.

Чтобы прекратить его до этого момента, вы можете использовать функцию выхода (int status), где status - это значение, возвращаемое любому запущенному приложению. 0 обычно указывает на состояние без ошибок


2
Обратите внимание, что этот ответ был объединен с вопросом Как выйти из программы на C ++? - SO 1116493 . Этот ответ был написан примерно за 6 лет до того, как был задан этот вопрос.
Джонатан Леффлер

11

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


3
Обратите внимание, что этот ответ был объединен с вопросом Как выйти из программы на C ++? - SO 1116493 . Этот ответ был написан примерно за 6 лет до того, как был задан этот вопрос.
Джонатан Леффлер

11

Если у вас есть ошибка где-то глубоко в коде, то либо сгенерируйте исключение, либо установите код ошибки. Всегда лучше генерировать исключение вместо установки кодов ошибок.


2
Обратите внимание, что этот ответ был объединен с вопросом Как выйти из программы на C ++? - SO 1116493 . Этот ответ был написан примерно за 6 лет до того, как был задан этот вопрос.
Джонатан Леффлер

9

Обычно вы используете exit()метод с соответствующим статусом выхода .

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


1
использование выхода МОЖЕТ означать, что у вас есть проблемы с дизайном. Если программа работает правильно, она должна просто завершиться, когда main 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);
    }
}

где second_main, в который помещается весь материал, который был первоначально помещен, т.е. исходный main переименовывается в second_main, и добавляется заглушка main выше. Это всего лишь небольшая хитрость, так что между лотком и уловом main не слишком много кода.

Если хотите, ловите другие типы исключений.
Мне очень нравится перехватывать строковые типы ошибок, такие как std :: string или char *, и печатать их в обработчике catch в main.

Использование подобных исключений, по крайней мере, позволяет вызывать деструкторы RAII, чтобы они могли выполнять очистку. Который может быть приятным и полезным.

В целом, обработка ошибок C - выход и сигналы - и обработка ошибок C ++ - исключения try / catch / throw - в лучшем случае несовместимы.

Затем, где вы обнаружите ошибку

throw "error message"

или какой-то более конкретный тип исключения.


Кстати: я хорошо знаю, что использование типа данных, такого как строка, которое может включать динамическое распределение памяти, не является хорошей идеей в или вокруг обработчика исключений для исключений, которые могут быть связаны с нехваткой памяти. Строковые константы в стиле C не являются проблемой.
Крейзи Глеу

1
Нет смысла звонить exitв вашей программе. Так как вы внутри main, вы можете просто return exitCode;.
Руслан

-1

Если ваш оператор if находится в цикле, вы можете использовать

 break; 

Если вы хотите избежать некоторого кода и продолжить цикл Используйте:

Продолжать;

Если ваше заявление if не в цикле, вы можете использовать:

 return 0;

Or 




  exit();

-2

Чувак ... exit()функция определена в stdlib.h

Так что вам нужно добавить препроцессор.

Положить include stdlib.hв заголовок раздела

Затем используйте, exit();где хотите, но не забывайте ставить целое число в круглые скобки выхода.

например:

exit(0);

-2

Если условие, которое я проверяю, действительно плохие новости, я делаю это:

*(int*) NULL= 0;

Это дает мне хороший coredump, откуда я могу изучить ситуацию.


4
Это вполне может быть оптимизировано, поскольку вызывает неопределенное поведение. Фактически, вся ветвь выполнения, имеющая такой оператор, может быть уничтожена компилятором.
Руслан

-2

Чтобы нарушить условие, используйте return (0);

Итак, в вашем случае это будет:

    if(x==1)
    {
        return 0;
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.