использование catch (...) (многоточие) для посмертного анализа - PullRequest
14 голосов
/ 02 февраля 2010

Кто-то в другом вопросе предложил использовать catch(...) для захвата всех необработанных в противном случае - неожиданных / непредвиденных исключений, окружив все main() блоком try{}catch(...){}.

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

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

Кроме того, какие предостережения связаны с этим. В частности:

  • Будет ли это хорошо работать с потоками, которые появятся позже?
  • не нарушит ли он обработку ошибок (захваченных в другом месте как сигнал)
  • не повлияет ли это на другие блоки try ... catch, неизбежно вложенные внутрь, которые предназначены для обработки ожидаемых исключений?

Ответы [ 6 ]

10 голосов
/ 02 февраля 2010

Да, это хорошая идея.

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

Тогда возникает вопрос, что с ними делать.
Некоторые ОС (см. MS и SE) предоставляют некоторые дополнительные средства отладки, поэтому полезно просто перезапустить исключение после того, как вы его перехватите (потому что в любом случае стек был размотан).

int main()
{
    try
    {
        /// All real code
    }
    // I see little point in catching other exceptions at this point 
    // (apart from better logging maybe). If the exception could have been caught
    // and fixed you should have done it before here.

    catch(std::exception const& e)
    {
         // Log e.what() Slightly better error message than ...
         throw;
    }
    catch(...)   // Catch all exceptions. Force the stack to unwind correctly.
    {
        // You may want to log something it seems polite.
        throw;  // Re-throw the exception so OS gives you a debug opportunity.
    }
}
  • Будет ли это хорошо работать с потоками, которые появятся позже?

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

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

  • не нарушит ли он обработку ошибок (захваченных в другом месте как сигнал)

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

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

Не должно влиять на другие обработчики.

4 голосов
/ 02 февраля 2010

Насколько я помню, catch(...) на Win32 перехватывает также SEH-исключения, и вы не хотите, чтобы делал это. Если вы получаете исключение SEH, то это происходит из-за чего-то очень страшного (в основном из-за нарушений прав доступа), поэтому вы больше не можете доверять своей среде. Почти все, что вы могли бы сделать, может потерпеть неудачу с другим исключением SEH, так что даже не стоит пытаться. Более того, некоторые исключения SEH предназначены для того, чтобы система могла их перехватить; Подробнее об этом здесь .

Итак, мой совет - использовать базовый класс исключений (например, std::exception) для всех ваших исключений и перехватывать только этот тип в "catchall"; Ваш код не может быть подготовлен для работы с другими видами исключений, поскольку они по определению неизвестны.

3 голосов
/ 02 февраля 2010

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

По вашим вопросам:

  • Я считаю, что глобальный блок catch не будет перехватывать исключения в другом потоке. Каждый поток имеет свое собственное пространство стека.
  • Я не уверен в этом.
  • Вложенные блоки try ... catch не затрагиваются и будут выполняться как обычно. Исключение распространяется вверх по стеку, пока не найдет блок try.
1 голос
/ 02 февраля 2010

Вы можете попробовать решение, которое я использую , если вы создаете приложение .net. Это фиксирует все необработанные исключения. Обычно я включаю код (с #ifndef DEBUG) для производственного кода, когда я не использую отладчик.

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

0 голосов
/ 02 февраля 2010

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

0 голосов
/ 02 февраля 2010

и как его восстановить (как получить доступ и признать, какой улов был звонил с)

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

try {
   ...
} catch (const SomeCustomException& e) {
   ...
} catch (const std::bad_alloc& e) {
   ...
} catch (const std::runtime_error& e) {
   // Show some diagnosic for generic runtime errors...
} catch (const std::exception& e) {
   // Show some diagnosic for any other unhandled std::exceptions...
} catch (...) {
   // Fallback for unknown errors.
   // Possibly rethrow or omit this if you think the OS can do something with it.
}

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

void MyExceptionHandler() {
   try {
      throw; // Rethrow the last exception.
   } catch (const SomeCustomException& e) {
      ...
   }
   ...
}

int main(int argc, char** argv) {
   try {
      ...
   } catch (...) {
      MyExceptionHandler();
   }
}
...