今日免费次数已满, 请开通会员/明日再来

7

如何在Visual Studio 2008 C++中捕获除以零错误(而不是其他错误;并能够访问异常信息)?

我尝试了以下代码:

try {
  int j=0;
  int i= 1/j;//actually, we call a DLL here, which has divide-by-zero
} catch(std::exception& e){
  printf("%s %s\n", e.what()); 
}  catch(...){
  printf("generic exception");
}

但是,这会进入通用的try...catch块。我知道Microsoft特有的__try可能在这里很有用,但我更喜欢标准C ++,而且无论如何,我有析构函数防止使用__try。
澄清:上面的代码被简化为讨论目的。实际上,除以零是一个bug,它发生在第三方DLL深处,我没有源代码。错误取决于我传递给库的参数(一个复杂结构的句柄),但不以任何明显的方式。因此,我希望能够优雅地恢复。

4
“我更喜欢标准的C++”,因为那样做不会出现未定义的行为,比如除以零。抛出异常是与平台有关的,如果捕获它的方式也是与平台相关的话,那也没什么大不了的。在不抛出除以零异常的平台上,你无法从这个dll中恢复此错误,因此你不需要一个平台无关的恢复方式。 - Steve Jessop
@Steve Jessop 在实际示例中,除零错误深入到一个DLL中,而我没有该源代码。 - Joshua Fox
9个回答

9
假设您不能简单地修复引发异常的代码原因(可能是因为您没有特定库的源代码,也可能是因为您无法在它们引起问题之前调整输入参数)。
您需要跨越一些障碍才能使其按照您的要求工作,但是这是可以实现的。
首先,您需要调用 _set_se_translator() (请参见此处),安装一个结构化异常处理翻译函数,然后您可以检查当 SEH 异常发生时传递给您的代码,并抛出适当的 C++ 异常。
void CSEHException::Translator::trans_func(
    unsigned int code, 
    EXCEPTION_POINTERS *pPointers)
{
   switch (code)
   {
       case FLT_DIVIDE_BY_ZERO : 
          throw CMyFunkyDivideByZeroException(code, pPointers);
       break;
   }

   // general C++ SEH exception for things we don't need to handle separately....
   throw CSEHException(code, pPointers);
}

然后,在C++中,您可以像通常一样捕获CMyFunkyDivideByZeroException()异常。

请注意,您需要在每个要翻译异常的线程上安装异常翻译函数。


只是想提一下,_set_se_translator 是基于每个线程的,并且仅在您控制创建所有线程时才有用。最有可能的情况是,在调用 DLL 时会遇到这种情况。您根本无法翻译它们的 SEHs。例如,Excel C AddIn DLL 可能会轻松崩溃整个 Excel 进程。只有 catch(...) 块才能捕获它们 - 但是您和 Excel 都不知道已经捕获了哪个 SEH。它可能是一个 EXCEPTION_ACCESS_VIOLATION(在其他操作系统上别名为 SIGSEGV),在这种情况下,您无论如何都注定要失败。我认为 _set_se_translator 会产生虚假的安全感。 - Andreas Spindler
Andreas,这是真的,但如果你在一个不属于你的线程上,那么你可以将函数包装在SEH异常处理程序中,并像正常的SEH异常一样处理它,因此你知道你捕获的异常类型(如果你想在处理C++异常的函数中捕获SEH异常,则必须绕过一些障碍,也许创建额外的shim函数)。噢,而且 EXCEPTION_ACCESS_VIOLATION并不意味着游戏结束——操作系统只是告诉你无法访问内存。 - Len Holgate
我是指在catch(...)的情况下,自然你不知道类型是什么。无论如何...除非你的程序是调试器、间谍软件或病毒,否则为什么要访问你没有拥有的内存呢?在大多数情况下,程序都是有问题的。即使是“无害”的NULL指针访问,在这种情况下程序也无法继续运行,因为它会再次崩溃。 - Andreas Spindler
我并不是说你想要访问你没有拥有的内存,只是“你注定会失败”这种说法有点过了,因为在很多情况下你仍然可以创建一个小型转储文件,并且可能仍然能够干净地关闭程序。 - Len Holgate
这对你来说是有效的,因为你已经得到了异常,但是对于任何试图使用此代码却没有得到任何东西的人来说,你可能需要使用一些丑陋的代码来启用浮点异常。以下是一个示例(这是我们在VS2015中使用的,对于糟糕的格式化表示抱歉)://设置浮点除以零异常 _clearfp();uint32 control_word = 0; _controlfp_s(&control_word, _MCW_EM, _MCW_EM); _controlfp_s(0, ~(uint32) (_EM_UNDERFLOW | _EM_OVERFLOW | _EM_ZERODIVIDE | _EM_INVALID), _MCW_EM); - Ben

9

C++本身并不将除以零视为异常。

引用Stroustrup的话:

"底层事件,如算术溢出和除以0, 通常由专门的低级机制处理,而不是由异常来处理。 这使得C++在算术方面与其他编程语言保持一致。 它也避免了在高度流水线化的架构中发生的问题,比如说除以0是异步的。"

"C++设计与演化" (Addison Wesley, 1994)

无论如何,异常永远不应该替代适当的前提条件处理。


1
谢谢,你的答案是正确的。但我无法处理前提条件,因为除零错误是一个深层次的第三方DLL中的bug,而我没有源代码。(我上面给出的代码是为了讨论目的而(过度)简化的。)我需要从这个bug中优雅地恢复过来。 - Joshua Fox

9
要在Visual C++中捕获除零异常,只需启用项目设置中的/EHa选项,即可使用try->catch (...)。请参见 项目属性 -> C/C++ -> 代码生成 -> 修改启用C++异常为“Yes With SEH Exceptions”。就这样!
详情请见: http://msdn.microsoft.com/en-us/library/1deeycx5(v=vs.80).aspx

7
你可以使用结构化异常处理(使用__try等)或安装结构化异常处理程序翻译器:_set_se_translator。这两种方法都是特定于操作系统的。

链接已失效,这是新链接:https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/set-se-translator - J. Merdich

3

你不能使用标准的C++来实现这个,因为它并不是标准的C++异常。它是一个结构化异常。如果要使用标准的C++异常,就需要在代码中使用throw exception;


2

为什么不在之前检查呢?相对于异常处理的上下文切换,一个简单的j == 0的性能问题是微不足道的。


谢谢,你的答案是正确的。但我无法处理前提条件,因为除以零是一个错误,它发生在第三方DLL中,我没有源代码。(我上面给出的代码是为了讨论目的而(过度)简化的。)我需要从错误中优雅地恢复。 - Joshua Fox

1
尝试下面的代码:
try
{ 
  const int j=0;
  if (j == 0) { throw std::exception("j was 0"); } 
  const int i= 1/j;    
}
catch(std::exception& e)
{ 
  printf("%s %s\n", e.what());  
}
catch(...)
{ 
  printf("generic exception"); 
}

当然,如果你可以在不使用异常的情况下完成这个任务,你可以这样做:
const int j = 0;
if (j == 0)
{
  /* do something about the bad pre-condition here */
}
else
{
  const int i = 1 / j;
}

根据澄清进行编辑:您必须事先弄清楚将要传递给第三方的输入是什么,这会导致他们发生除零错误,并在调用他们的函数之前处理它。


1
一个好的方法是使用安全的面向对象封装,比如SafeInt。它似乎也已经集成在Visual Studio 2010中。
更新:
如果除零操作发生在第三方代码中,你唯一的选择是使用SEH或者类似的东西,就像Seb Rose所回答的那样

0

你可以使用try-except语句。 但是,不要忘记为每个线程设置它。


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