Должна ли программа на C ++ перехватывать все исключения и предотвращать всплытие исключений после main ()?


29

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

В то время это казалось странным, но убедительным. Было совершенно бессмысленно, что C ++ должен «наказать» программистов за то, что они не перехватили все исключения, но доказательства, которые были до меня, похоже, подтверждали это. Для рассматриваемого проекта программы, которые выдавали неисследованные исключения, действительно входили в странное состояние зомби - или, как я подозреваю, причина была в том, что процесс посреди нежелательного дампа ядра необычайно трудно остановить.

(Для тех, кто интересуется, почему это не было более очевидным в то время: проект генерировал большое количество выходных данных в нескольких файлах из нескольких процессов, которые эффективно скрывали любые aborted (core dumped)сообщения, и в этом конкретном случае посмертная проверка дампов ядра не была не является важной техникой отладки, поэтому дампы ядра не были особо продуманы. Проблемы с программой обычно не зависели от состояния, накопленного во многих событиях с течением времени долгоживущей программой, а скорее исходных входных данных для недолговечной программы (< 1 час), поэтому было практичнее просто перезапустить программу с теми же входными данными из отладочной сборки или в отладчике, чтобы получить больше информации.)

В настоящее время я не уверен в том, есть ли какое-либо существенное преимущество или недостаток в вылове исключений исключительно с целью предотвращения выхода исключений main().

Небольшое преимущество, которое я могу придумать, позволяя исключениям возникать в прошлом, main()заключается в том, что это приводит к тому, что результат std::exception::what()выводится на терминал (по крайней мере, для программ, скомпилированных gcc в Linux). С другой стороны, это тривиально, если вместо этого перехватить все исключения, полученные из, std::exceptionи распечатать результат, std::exception::what()и если желательно напечатать сообщение из исключения, которое не является производным, std::exceptionоно должно быть перехвачено перед отправкой main(), чтобы напечатать сообщение.

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

Кстати, я не думаю, что когда-либо видел what(): ...сообщение по умолчанию, распечатанное широко распространенной программой после сбоя.

Что, если таковые имеются, являются сильными аргументами за или против того, чтобы исключения C ++ всплыли в прошлом main()?

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




@gnat Мой вопрос немного более конкретен. Речь идет об исключениях в C ++, которые ни при каких обстоятельствах не могут быть обработаны, а не обработаны в любом языке программирования.
Praxeolitic

Для многопоточных программ, вещи могут быть более сложными и более экзотическими (исключения WRT)
Basile Starynkevitch

1
Специалисты по программному обеспечению для Ariane 5 наверняка хотели бы, чтобы они уловили все исключения во время первого запуска ...
Эрик Тауэрс,

Ответы:


25

Одной из проблем, связанных с пропуском исключений после main, является то, что программа завершится вызовом, к std::terminateкоторому следует обращаться по умолчанию std::abort. Реализация определяется только в том случае, если раскрутка стека выполняется перед вызовом, terminateпоэтому ваша программа может завершиться без вызова одного деструктора! Если у вас есть какой-то ресурс, который действительно необходимо восстановить с помощью вызова деструктора, вы в затруднительном положении ...


12
Если у вас есть какой-то ресурс, который действительно необходимо восстановить с помощью вызова деструктора, вы в затруднительном положении ... => Учитывая, что (к сожалению) C ++ довольно подвержен сбоям, и это не говоря уже о том, что ОС может решить убить Ваша программа в любое время (например, OOM killer в Unix), ваша программа (и ее окружение) должны быть адаптированы для поддержки сбоев.
Матье М.

@MatthieuM. Вы говорите, что деструкторы не являются хорошим местом для очистки ресурсов, которая должна происходить даже во время сбоя?
Праксеолит

6
@Praxeolitic: я говорю, что не все сбои разматывают стек, например std::abort, нет и, следовательно, ошибочные утверждения тоже не делают. Также стоит отметить, что в современных ОС сама ОС будет очищать много ресурсов (память, дескрипторы файлов, ...), которые связаны с идентификатором процесса. Наконец, я также намекал на «Защиту в глубине»: ненадежно ожидать, что все другие процессы защищены от ошибок и всегда будут высвобождать полученные ими ресурсы (сеансы, красиво заканчивать написание файлов, ...), которые вы должны запланировать это ...
Матье М.

3
@Praxeolitic Нет, ваше программное обеспечение не должно повреждать данные при сбое питания. И если вы можете управлять этим, вы также можете не повреждать данные после необработанного исключения.
user253751

1
@Praxeolitic: На самом деле это существует. Вы хотите прослушать WM_POWERBROADCASTсообщение. Это работает только в том случае, если ваш компьютер работает от батареи (если вы используете ноутбук или ИБП).
Брайан

28

Основная причина, по которой вы не позволяете избежать исключений, mainзаключается в том, что в противном случае вы теряете возможность контролировать то, как о проблеме сообщают ваши пользователи.

Для программы, которая не предназначена для использования в течение длительного времени или широко распространена, может быть приемлемым, чтобы о непредвиденных ошибках сообщалось любым способом, которым ОС решает это сделать (например, показывая диалоговое окно с сообщением об ошибке в вашем лице в Windows ).

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


Вы уверены, что поведение по умолчанию для исключения C ++, которое оставляет, main() имеет много общего с ОС? ОС может ничего не знать о C ++. Я предположил, что это определяется кодом, который компилятор вставляет где-то в программу.
Praxeolitic

5
@Praxeolitic: более того, ОС устанавливает соглашения, а компилятор генерирует код для выполнения этих соглашений, когда программа неожиданно завершает работу. Суть в том, что следует избегать неожиданного завершения программы всякий раз, когда это возможно.
Барт ван Инген Шенау

Вы теряете контроль только в рамках стандарта C ++. Должен быть доступен специфичный для реализации способ обработки таких «сбоев». C ++ обычно не работает в вакууме.
Мартин Ба

Это диалоговое окно с сообщением об ошибке в вашем личном кабинете может быть включено / выключено в реестре, чтобы оно того стоило, но для этого вам потребуется контроль над реестром, и большинство программ не работают в режиме киоска ОС).
PerryC

11

TL; DR : Что говорит спецификация?


Технический обход ...

Когда выдается исключение и никакой обработчик не готов к нему:

  • это реализация, определенная независимо от того, будет ли стек размотан или нет
  • std::terminate называется, который по умолчанию прерывается
  • в зависимости от настроек среды прерывание может оставить или не оставить отчет о сбое

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


Уловить ли все исключения или нет, в конечном счете, вопрос спецификации:

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

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

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


1
Это. Решающие факторы относительно В основном выходят за рамки C ++.
Мартин Ба

4

Исключение, которое вы ловите, дает вам возможность напечатать приятное сообщение об ошибке или даже попытаться исправить ошибку (возможно, просто перезапустив приложение).

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

Так что это компромисс.


4

В тот момент, когда вы знаете, что вам нужно прервать, продолжайте и звоните std::terminateуже, чтобы сократить дальнейший ущерб.

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

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

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


1

Большую часть времени грациозно разбивать - это хорошо, но есть и компромиссы. Иногда это хорошая вещь, чтобы потерпеть крах. Я должен отметить, что я в основном думаю об отладке в очень большом. Для простой программы - хотя это все еще может быть полезно, она нигде не так полезна, как с очень сложной программой (или несколькими сложными программами, взаимодействующими).

Вы не хотите аварийно завершать работу публично (хотя это действительно неизбежно - аварийное завершение программ, и действительно математически проверяемая не сбойная программа - это не то, о чем мы здесь говорим). Подумайте о BSOD Билла Гейтса во время демонстрации - плохо, правда? Тем не менее, мы можем попытаться выяснить, почему мы потерпели крах, и не потерпели крах таким же образом.

Я включил функцию отчетов об ошибках Windows, которая создает локальные аварийные дампы для необработанных исключений. Это чудесно, если у вас есть файлы символов, связанные с вашей (сбойной) сборкой. Интересно, потому что я создал этот инструмент, я хочу , чтобы разбить более - потому что я узнал из каждой аварии. Тогда я могу исправлять ошибки и меньше вылетать.

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

Вы можете прочитать больше о локальных дампах сбоя здесь, если вам интересно: Сбор дампов пользовательского режима


-6

В конечном счете, если исключение всплывет после main (), оно может привести к сбою вашего приложения, и, на мой взгляд, приложение никогда не должно аварийно завершить работу. Если это нормально, чтобы вывести приложение из строя в одном месте, то почему нигде? Зачем вообще заниматься обработкой исключений? (Сарказм, на самом деле не предлагая этого ...)

У вас может быть глобальная попытка / отлов, которая печатает элегантное сообщение, сообщающее пользователю, что произошла неустранимая ошибка, и что им нужно отправить электронное письмо Бобу об этом или о чем-то подобном, но падение совершенно непрофессионально и недопустимо. Это легко предотвратить, и вы можете информировать пользователя о том, что только что произошло, и о том, как исправить ситуацию, чтобы она больше не повторилась.

Сбой - это чисто любительский час.


8
Итак, лучше притвориться, что все работает правильно и вместо этого перезаписать все постоянное хранилище?
Дедупликатор

1
@Deduplicator: Нет: вы всегда можете перехватить исключительную ситуацию и обработать ее.
Джорджио

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

8
но сбой совершенно непрофессиональный и недопустимый => тратить время на работу с бесполезными функциями непрофессионально: сбой имеет значение только в том случае, если он имеет значение для конечного пользователя, если это не так, а затем позволить ему произойти сбой (и получить отчет о сбое с дампом памяти) более экономичный
Матье М.

Мэтью М. Если вам действительно потребуется столько усилий, чтобы записать ошибку, сохранить все открытые файлы и нарисовать дружеское сообщение на экране, я не знаю, что сказать. Если вы думаете, что бесполезно терпеть неудачу, бесполезно, вам, вероятно, следует поговорить с тем, кто занимается технической поддержкой вашего кода.
Роджер Хилл
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.