【问题标题】:using catch(...) (ellipsis) for post-mortem analysis使用 catch(...)(省略号)进行事后分析
【发布时间】:2011-01-12 02:11:24
【问题描述】:

另一个问题中的某个人建议使用 catch(...) 来捕获所有其他未处理的 - 意外/不可预见的异常,方法是用 try{}catch(...){} 块包围整个 main()

这听起来是一个有趣的想法,可以节省大量调试程序的时间,并至少留下一点线索。

问题的本质是可以通过这种方式恢复哪些信息(除了我留下的任何调试全局变量),以及如何恢复它(如何访问并识别任何 catch 被调用)

此外,还有哪些注意事项与之相关。特别是:

  • 它会与稍后发芽的线程一起使用吗?
  • 它不会破坏处理段错误(在其他地方作为信号捕获)
  • 它不会影响其他不可避免地嵌套在其中的 try...catch 块,这些块用于处理预期的异常吗?

【问题讨论】:

    标签: c++ exception-handling debugging try-catch ellipsis


    【解决方案1】:

    是的,这是个好主意。

    如果你让一个异常逃逸 main 它是实现定义的天气堆栈在应用程序关闭之前展开。所以在我看来,你必须在 main 中捕获所有异常。

    然后问题就变成了如何处理它们。
    某些操作系统(请参阅 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 块,用于处理预期的异常吗?

    应该对其他处理程序没有影响。

    【讨论】:

      【解决方案2】:

      据我所知,Win32 上的catch(...) 也捕获 SEH 异常,而您不想这样做。如果你得到一个 SEH 异常,那是因为发生了一些非常可怕的事情(主要是访问冲突),所以你不能再信任你的环境了。几乎你能做的所有事情都可能因另一个 SEH 异常而失败,所以它甚至不值得尝试。此外,一些 SEH 异常旨在被系统捕获;更多关于这个here

      所以,我的建议是为所有异常使用基本异常类(例如std::exception),并在“catchall”中捕获该类型;您的代码无法准备好处理其他类型的异常,因为它们在定义上是未知的。

      【讨论】:

      • 如果我用 throw; 结束我的 catch 块怎么办?无论如何,当 SEH 发生时,除了递归进入 SEH(然后看门狗会杀了我)之外,不会发生更糟糕的事情。
      • 即使您重新抛出异常,您的代码仍然会将某些正常情况作为异常处理(例如,堆栈保护页面上的访问冲突,由系统自动扩展堆栈处理)。如果您在异常处理程序中生成了 SEH 异常,则它不会被您的 catchall 捕获(为此您需要设置一个全局 SEH 处理程序),而您的应用程序只会崩溃;尽管如此,这仍然会使 minidump 变得毫无用处,因为所有 SEH 异常都会追溯到包罗万象,而不是真正有问题的代码。
      • 我将把它作为可选的调试工具。如果非段错误异常导致问题,则正常关闭它。
      • catch(...) 在 Windows 下是否捕获 SEH 异常是编译器特定的。对于 Microsoft 编译器,vc7 的 catch(...) 总是捕获 SEH 异常。对于 vc8 及更高版本,有一个编译器选项可以启用该行为 (/EHa),但默认情况下它是关闭的。
      • 有趣,我不知道它(实际上我仍然使用 7.1,所以我只知道它的行为)。
      【解决方案3】:

      全局 try catch 块对于生产系统很有用,以避免向用户显示讨厌的消息。在开发过程中,我认为最好避免这种情况。

      关于您的问题:

      • 我相信全局 catch 块不会在另一个线程中捕获异常。每个线程都有自己的堆栈空间。
      • 对此我不确定。
      • 嵌套的 try...catch 块不受影响,将照常执行。异常会沿堆栈向上传播,直到找到 try 块。

      【讨论】:

      • 如果“避免显示讨厌的消息”是指“用可读的消息替换讨厌的消息”,那么我同意。如果您只是想删除错误消息,那只会让用户感到困惑。
      • 这就是我的意思,向用户显示一条可读消息,而不是解密的堆栈跟踪。
      • 在大多数系统上,如果异常从线程入口点逃脱,则应用程序会毫不客气地终止。导致应用程序停止死机而不展开主线程堆栈。但请仔细阅读您的线程文档以了解详细信息。但通常最好在线程库中捕获所有异常。
      【解决方案4】:

      如果您正在制作 .net 应用程序,您可以尝试a solution I use。这捕获了所有未处理的异常。我通常只在不使用调试器时为生产代码启用代码(使用#ifndef DEBUG)。

      值得指出,因为 kgiannakakis 提到您无法在其他线程中捕获异常,但您可以在这些线程中使用相同的 try-catch 方案并将异常发布回主线程,您可以在其中重新抛出它们获取完整堆栈跟踪出了什么问题。

      【讨论】:

        【解决方案5】:

        以及如何恢复它(如何访问 并识别出任何捕获物 调用)

        如果您的意思是如何恢复抛出的异常类型,您可以在回退到 catch (...) 之前链接特定类型的 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();
           }
        }
        

        【讨论】:

        • 现在您已经捕获了一个未知异常,您打算如何处理它?
        • @Piskvor:如果您已经用尽了所有您知道(或关心)的异常类型,那么除了显示“未知内部错误”消息并终止之外别无他法。
        • @jamesdlin: ...如果没有 try 块,这无论如何都会发生,那何必呢?
        • @Piskvor:我想,但应用程序仍然可以提供比默认更友好的错误消息,这可能充满了行话。它甚至可以包括支持说明。
        • 捕捉和使用 EXIT_FAILURE 的坏主意。一些操作系统提供额外的工具来调试逃逸 main() 的异常。接住并重新投掷。传播到这里的任何异常都没有合理的纠正潜力(如果确实如此,那么在到达这里之前就已经纠正了)。
        【解决方案6】:

        因为没有可以查询的类型/对象信息,所以包罗万象不会非常有用。但是,如果您可以确保应用程序引发的所有 异常都源自单个基础对象,则可以对基础异常使用 catch 块。但这并不是万能的。

        【讨论】:

        • 对不起,我完全误读了你的回答——我会删除评论。
        猜你喜欢
        • 2021-02-12
        • 2019-02-13
        • 2018-10-06
        • 1970-01-01
        • 2021-05-24
        • 2019-06-28
        • 2012-08-09
        • 1970-01-01
        相关资源
        最近更新 更多