C++:捕获除以零错误

Posted

技术标签:

【中文标题】C++:捕获除以零错误【英文标题】:C++ : Catch a divide by zero error 【发布时间】:2021-08-03 03:46:39 【问题描述】:

这是一段简单的代码,其中发生了被零除。我试图抓住它:

#include <iostream>

int main(int argc, char *argv[]) 
    int Dividend = 10;
    int Divisor = 0;

    try 
        std::cout << Dividend / Divisor;
     catch(...) 
        std::cout << "Error.";
    
    return 0;

但应用程序无论如何都会崩溃(即使我选择了 MinGW-fexceptions 选项)。

是否有可能捕捉到这样的异常(我理解的不是 C++ 异常,而是 FPU 异常)?

我知道我可以检查除数 before 除数,但我假设,因为除以零很少见(至少在我的应用程序中),它会更多尝试除法(并在发生错误时捕获错误)比在除法之前每次测试除数时有效。

我正在一台 WindowsXP 计算机上进行这些测试,但希望让它跨平台。

【问题讨论】:

if (Divisor == 0) std::cout &lt;&lt; "Error."; exit; 。然后,您消除了导致这种情况发生的所有原因。然后,您可以删除条件并运送您的产品,而不会被零除。 assert(Divisor != 0) 会更规范。不要忘记通过调试器运行开发!) 这甚至不是 FPU 异常,而是 CPU 异常。 FPU 处理浮点数,但你会得到 NaN 或无穷大,而不是崩溃。 经验法则:总是(如果你错过了重点:总是!)在进行计算之前检查你的输入,你可以避免这些类型的问题。 “我做了这样的假设,因为除以零很少见(至少在我的应用程序中),它会更有效” - 然后不要这样做运行分析器之前的假设。突然做出假设是优化失败的一种方式。相信您的优化器和 CPU 的分支预测。 【参考方案1】:

除以零在 C++ 中也不例外,请参阅https://web.archive.org/web/20121227152410/http://www.jdl.co.uk/briefings/divByZeroInCpp.html

【讨论】:

OP 在问题中说。 “有没有可能捕捉到这样的异常(我理解的不是C++异常,而是FPU异常)?” OP 应该只做一个零检查:) 不过应该​​是个例外【参考方案2】:

除以零是一个逻辑错误,是程序员的错误。你不应该试图应对它,你应该调试并消除它。此外,捕获异常非常昂贵——比除数检查要昂贵得多。

您可以使用结构化异常处理来捕获除以零错误。如何实现取决于您的编译器。 MSVC 提供了将结构化异常捕获为catch(...) 的功能,还提供了将结构化异常转换为常规异常的功能,以及提供__try/__except/__finally。但是,我对 MinGW 还不够熟悉,无法告诉您如何在该编译器中进行操作。

【讨论】:

+1 建议修复墙上的洞,而不是试图在上面挂一张照片。 +1 表示检查条件比捕获异常更便宜。 检查只比非常琐碎代码的异常便宜 并非总是如此 - 考虑科学计算,其中 0.0 通常有一些有限但发生的概率很小。 我不同意“除以零==程序员错误”。想象你在一堆不可预测的输入数据(来自外部源、磁盘文件、网络等的数据)上运行一个复杂的算法,这可能会导致除以零,然后你意识到在每次使用 / 运算符之前检查零非常麻烦。那么,像 Windows SEH 这样的东西就是一个很好的救援。【参考方案3】:

    没有 语言标准的捕捉方式 来自 CPU 的除零。

    不要过早地“优化”掉一个 分支。是你的应用 真的 在这种情况下 CPU 受限?我对此表示怀疑,而且它不是真正的 如果您破坏代码,则进行优化。 否则,我可以制作你的代码 更快:

    int main(int argc, char *argv[])  /* Fastest program ever! */ 
    

【讨论】:

【参考方案4】:

好吧,如果有一个关于这个的异常处理,实际上某些组件需要进行检查。因此,如果您自己检查它,您不会丢失任何东西。而且没有什么比简单的比较语句更快的了(一条 CPU 指令“如果等于 0 则跳转”或类似的东西,不记得名字了)

【讨论】:

【参考方案5】:

这也不例外。这是一个错误,在硬件级别确定并返回给操作系统,然后操作系统以某种特定于操作系统的方式通知您的程序(例如,通过杀死过程)。

相信在这种情况下发生的事情不是例外,而是一个信号。如果是这种情况:操作系统会中断您程序的主控制流并调用信号处理程序,然后信号处理程序会终止程序的操作。

这与取消引用空指针时出现的错误类型相同(然后您的程序因 SIGSEGV 信号而崩溃,分段错误)。

您可以尝试使用 &lt;csignal&gt; 标头中的函数来尝试为 SIGFPE 信号提供自定义处理程序(它用于浮点异常,但也可能会引发整数除以零的情况 - 我'我真的不确定这里)。但是您应该注意,信号处理是依赖于操作系统的,并且 MinGW 以某种方式“模拟”了 Windows 环境下的 POSIX 信号。


这是在 MinGW 4.5、Windows 7 上的测试:

#include <csignal>
#include <iostream>

using namespace std;

void handler(int a) 
    cout << "Signal " << a << " here!" << endl;


int main() 
    signal(SIGFPE, handler);
    int a = 1/0;

输出:

信号 8 在这里!

在执行信号处理程序后,系统立即终止进程并显示错误消息。

使用它,您可以在除以零或空指针取消引用后关闭任何资源或记录错误...但 与异常不同,即使在异常情况下也不能控制程序的流程。 一个有效的程序不应该那样做。捕获这些信号仅对调试/诊断有用。

(有一些有用的信号通常在低级编程中非常有用,并且不会导致您的程序在处理程序之后立即被杀死,但这是一个很深的话题)。

【讨论】:

Windows 有一个很好的基于帧的异常处理工具(并且可能 C++ 异常框架是建立在 Windows 工具上的)。可以通过编译器开关将硬件异常转换为 C++ 异常,但您需要小心 - 过分热心的故障处理会使您进入未定义状态。 (注意,顺便说一句,我坚持将除零称为异常:操作系统和硬件将确认它是异常,它只是不是 C++ 异常)。已编辑:哎呀,忽略了问题的 MinGW 部分。这可能会发生很大变化。不管怎样,我会把我的胡扯留在这里。 您能否为信号(尤其是那些不杀死进程的信号)添加一些好的参考? 它是特定于操作系统的。 POSIX 定义了这些; Windows 不是 POSIX 兼容的,但 MinGW 尽最大努力缩小差距。对于 Linux,您有 this man page,“非致命”信号的常见示例是 SIGSTOP、SIGTSTP(临时停止)和 SIGCONT(继续)。 同一个人不受 POSIX 限制,因此能够使用 catch 块捕获此类异常。让我们看看它还需要多少年,直到该功能进入 LINUX。匹配选项(-fnon-call-exceptions)已经可用,但适用于苹果操作系统。【参考方案6】:

不知何故,真正的解释仍然缺失。

是否有可能捕捉到这样的异常(我理解的不是 C++ 异常,而是 FPU 异常)?

是的,您的 catch 块应该适用于某些编译器。但问题是您的异常不是 FPU 异常。你正在做整数除法。我不知道这是否也是一个可捕获的错误,但它不是 FPU 异常,它使用了 IEEE 浮点数表示的特性。

【讨论】:

FPU 异常不会被标准 C++ 中的catch(...) 捕获。但至少有一个编译器支持它,很久以前。也就是说,Visual C++ 曾经将其作为语言扩展。我相信,似乎记得,该扩展存在于版本 6.0 中,这是预标准的,并在版本 7.0 中被删除,这是第一个 Visual Studio.NET 版本和 C++98 之后的版本。【参考方案7】:

正如其他人所说,这不是一个例外,它只是生成一个 NaN 或 Inf。

零除法只是实现这一目标的一种方法。如果您进行大量数学运算,则有很多种方法,例如log(not_positive_number)exp(big_number) 等。

如果您可以在进行计算之前检查有效参数,那么就这样做,但有时这很难做到,因此您可能需要生成并处理异常。

在 MSVC 中有一个头文件 #include &lt;float.h&gt; 包含一个函数 _finite(x),它告诉一个数字是否是有限的。 我很确定 MinGW 也有类似的东西。 您可以在计算后对其进行测试并抛出/捕获您自己的异常或其他任何东西。

【讨论】:

这对于浮点数来说是正确的,但是这个例子是整数,因此没有 NaN 也没有 inf,CPU 会生成一个信号/结构化异常/你想调用它的任何东西。 @Suma: Touche'。 BTW,好久没和你吵了。希望一切都好。【参考方案8】:

在 Windows(使用 Visual C++)上,试试这个:

BOOL SafeDiv(INT32 dividend, INT32 divisor, INT32 *pResult)

    __try 
     
        *pResult = dividend / divisor; 
     
    __except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? 
             EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
     
        return FALSE;
    
    return TRUE;

MSDN:http://msdn.microsoft.com/en-us/library/ms681409(v=vs.85).aspx

【讨论】:

不需要使用这个古老的 try-catch,但普通的 C++ try-catch with (...) 也可以这样做——假设使用了编译器选项 /EHa。【参考方案9】:

为了避免无限“信号 8 这里!”消息,只需在 Kos nice 代码中添加“退出”即可:

#include <csignal>
#include <iostream>
#include <cstdlib> // exit

using namespace std;

void handler(int a) 
    cout << "Signal " << a << " here!" << endl;
    exit(1);


int main() 
    signal(SIGFPE, handler);
    int a = 1/0;

【讨论】:

以上是关于C++:捕获除以零错误的主要内容,如果未能解决你的问题,请参考以下文章

Erlang中除以零的精确异常类型

使用 datediff 时遇到除以零错误

如何避免 SQL 中的“除以零”错误?

处理“除以零错误遇到错误”

std::thread,在线程中抛出异常会导致 Visual C++ 中的中止错误

软件开发入门教程网之​​C++ 信号处理