C++未处理异常

9

C++有没有提供一种方式,在未处理的异常发生时“显示”一些可视化内容?

我的目标是在实际发生未处理的异常时执行类似于assert(unhandled exception.msg())的操作(如下面的示例):

#include <stdexcept>

void foo() {
   throw std::runtime_error("Message!");
}

int main() {
 foo();
}

我期望这种代码不会立即终止(因为异常未处理),而是显示自定义的断言消息(实际上是Message!)。

这是否可能?


3
为什么不在main函数里加一个try/catch块呢? - GManNickG
1
@GMan:全局构造函数或析构函数也可以在 main 函数之外抛出异常。对于析构函数的情况,堆栈展开可能无法到达 main 函数。 - Potatoswatter
@Potatoswatter:确实,我更关心他的具体例子。 - GManNickG
此外,main 线程无法看到其他线程抛出的异常。 - Sz.
8个回答

15

根据标准,没有指定一种方法来显示未捕获异常的消息。但是在许多平台上,仍然有可能实现。在Windows上,您可以使用SetUnhandledExceptionFilter并提取C++异常信息。使用g++(适当版本),终止处理程序可以通过以下代码访问未捕获的异常:

   void terminate_handler()
   {
       try { throw; }
       catch(const std::exception& e) { log(e.what()); }
       catch(...) {}
   }

实际上,g++的默认终止处理程序类似于此。您可以使用set_terminate设置终止处理程序。

简而言之,没有通用的C++方法,但取决于您的平台有不同的方法。


我喜欢这个。标准可能没有明确要求,但它确实表示仅在捕获该异常的块退出时才停用异常。由于在终止之前不可能发生这种情况,因此这应该是可移植的,或者至少对所有东西都是向前兼容的。相同的技巧也适用于 unexpected,这将允许诊断不合法的 throw-on-unwind。 - Potatoswatter
1
此外,它可以捕获从全局构造函数和析构函数中抛出的异常,而main中的catch块无法做到这一点。 - Potatoswatter
@Potatoswatter 我见过至少一个设置,这个技巧并不真正起作用,我不敢声称自己对标准(以及我们在异常处理方面做的其他事情)足够了解,无法确定是否可行,但我知道它可以在Linux上使用现代gcc实现。 - Logan Capaldo
我还发现有些情况下,程序可能会终止但却没有异常。因此,在尝试重新抛出异常之前,我使用 abi::__cxa_current_exception_type 来确定是否存在异常。显然,这是一种非可移植的 GCC 扩展。 - Logan Capaldo
不,从实际情况来看,我不认为它是可移植的,在当前时间范围内。好吧,如果您也设置了一个意外处理程序,那么重新抛出就会将 terminate 转换为 unexpected。您可以添加一些逻辑以继续沿着适当的处理程序链进行(即 unexpected 调用替换后的 terminate 处理程序),而程序的其余部分则毫不知情,对吧? - Potatoswatter
1
根据https://en.cppreference.com/w/cpp/error/terminate,有2种方法可以在无异常的情况下进入terminate_handler:您的程序调用了terminate()和“销毁或分配加入的std :: thread”。 - Pellekrino

6
Microsoft Visual C++允许你挂接未处理的C++异常像这样。这是标准STL行为
你可以通过调用set_terminate来设置处理程序。建议你的处理程序不要做太多的工作,然后终止程序,但我不明白为什么不能通过assert发送信号 - 尽管你无法访问导致问题的异常。

1
终止处理程序不应断言。可以使用 printf(或更好的方式,直接将其写入STDERR_FILENO),并调用它替换的终止处理程序。另外,请参阅Logan的答案和讨论,了解为什么在那个点上仍应该可以访问异常。 - Potatoswatter
@Potatoswatter - 你的说法是否适用于所有情况 - 换句话说,断言是可能的,但不推荐吗?谢谢。 - Steve Townsend
可以始终进行断言,但建议任何处理程序函数都要尾调用先前安装的处理程序。 - Potatoswatter

5
我认为以下这个万能语句对您有所帮助:

我觉得你可以使用以下的万能语句:

int main() {
 try {
   foo();
 catch (...) {
   // Do something with the unhandled exception.
 }
}

6
值得注意的是,在此方法中异常本身不可供内省。这是因为您可能没有抛出std::exception实例,C++允许您抛出任何类型的值,例如int或其他类型。您可以通过在catch(...)之前也加入一个catch (std::exception &e)或任何您想要捕获的类型来解决这个问题。 - SingleNegationElimination
在Windows上,您必须小心处理(...)捕获内部抛出的结构化异常(例如访问冲突)。在此类情况下继续执行可能很危险。 - seand
无论发生什么异常,使用此方法可能无法继续执行。这只会在堆栈完全展开到 main() 之后才捕获异常。此时,您可以考虑 exec(argv[0]) - SingleNegationElimination

4

1
如果我正确理解你的问题,你正在询问是否可以重载throw(更改其默认行为),以便它执行用户定义的操作。不,你不能这样做。
编辑:既然你坚持要这么做,那么这是一个坏主意™:
#include <iostream>
#include <stdlib.h>
#include <windows.h>

void monkey() {
   throw std::exception("poop!");
}

LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *lpTopLevelExceptionFilter) {
    std::cout << "poop was thrown!" << std::endl;
    return EXCEPTION_EXECUTE_HANDLER;
  }

int main() {
    SetUnhandledExceptionFilter(&MyUnhandledExceptionFilter);
    monkey();
    return 1;
}

再说一遍,这是一个非常糟糕的想法,而且显然依赖于平台,但它确实有效。


实际上不是 throw。我想在出现未处理的异常情况时更改默认行为。 - Yippie-Ki-Yay
2
基本上,当您throw一个未处理的异常时 :-P - David Titarenco

1

是的,这是可能的。请看:

#include <iostream>
#include <exception>

void foo() 
{
   throw std::exception("Message!");
}

int main() 
{
  try
  {
    foo();
  }
  catch (std::exception& e)
  {
    std::cout << "Got exception: " << e.what() << std::endl;
  }

  return 0;
}

0

C++标准是终止处理程序-正如其他人所说

如果你想更好地跟踪throws,那么这就是我们所做的

我们有一个宏Throw,记录文件名和行号以及消息,然后抛出。它采用printf样式的可变参数消息。

Throw(proj::FooException, "Fingle %s unable to process bar %d", fingle.c_str(), barNo);

我得到了一个不错的日志信息

Throw FooException from nargle.cpp:42 Fingle barf is unable to process bar 99

1
这里有两个问题。首先,它会为捕获的异常添加额外的处理。这不是什么大问题,因为如果发生足够多的次数,开销(或日志中的噪音)成为问题,那么你就不应该在这种情况下使用异常。第二个问题是一个更实际的关注点。当异常条件发生时,额外的处理可能也会失败。预分配错误消息缓冲区可以帮助,但最终你需要确保在错误报告期间防止失败。 - Ben Voigt
由于是我主动抛出异常(而不是运行时),所以我不处于灾难性(无内存、无堆栈、死亡堆)模式,因此处理程序可以正常工作。如果不能,则我已经挂了。我在非常大的商业企业产品(100kloc)中使用这种技术 - 没有问题。我可以调整日志记录级别以抑制开销。 - pm100

0

如果你真的对导致程序失败的原因感兴趣,那么使用后期调试器检查进程映像可能会对你有所帮助。具体技术因操作系统而异,但基本的步骤是首先启用核心转储,并在编译程序时启用调试符号。一旦程序崩溃,操作系统将把其内存复制到磁盘上,然后你可以检查程序在崩溃时的状态。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接