捕获异常:除以零

Catching exception: divide by zero

本文关键字:捕获异常      更新时间:2023-10-16

当我尝试除以 0 时,以下代码没有捕获异常。我是否需要引发异常,或者计算机是否在运行时自动引发异常?

int i = 0;
cin >> i;  // what if someone enters zero?
try {
    i = 5/i;
}
catch (std::logic_error e) {
    cerr << e.what();
}

您需要自己检查并引发异常。整数除以零在标准C++中也不例外。浮点除/余数也不是零,但至少具有可能产生的特定有理值(例如各种NaN/Inf值(。

ISO C++20标准(本答案中使用的迭代([stdexcept.syn]部分中列出的例外情况是:

namespace std {
    class logic_error;
        class domain_error;
        class invalid_argument;
        class length_error;
        class out_of_range;
    class runtime_error;
        class range_error;
        class overflow_error;
        class underflow_error;
}

现在你可以非常有说服力地争辩说,overflow_error(浮点数产生的无穷大可以被认为是溢出IEEE754(或domain_error(毕竟,这是输入值的问题(是指示除以零的理想选择。

但是,第 [expr.mul] 节特别指出(对于整数和浮点除法以及整数余数(:

如果 /% 的第二个操作数为零,则行为未定义。

因此,它可能会引发这些(或任何其他(异常。它还可以格式化您的硬盘并嘲笑:-(

<小时 />

如果你想实现这样的野兽,你可以在下面的程序中使用类似intDivEx的东西(使用溢出变体(:

#include <iostream>
#include <stdexcept>
// Integer division/remainder, catching divide by zero.
inline int intDivEx (int numerator, int denominator) {
    if (denominator == 0)
        throw std::overflow_error("Divide by zero exception");
    return numerator / denominator;
}
inline int intModEx (int numerator, int denominator) {
    if (denominator == 0)
        throw std::overflow_error("Divide by zero exception");
    return numerator % denominator;
}
int main (void) {
    int i = 42;
    try {
        i = intDivEx (10, 0);
    } catch (std::overflow_error &e) {
        std::cout << e.what() << " -> ";
    }
    std::cout << i << std::endl;
    try {
        i = intDivEx (10, 2);
    } catch (std::overflow_error &e) {
        std::cout << e.what() << " -> ";
    }
    std::cout << i << std::endl;
    return 0;
}

这输出:

Divide by zero exception -> 42
5

您可以看到它抛出并捕获除以零大小写的异常(保持返回变量不变(。

使用来自 ExcessPhase 的评论进行了更新

GCC(至少是 4.8 版(将允许您模拟此行为:

#include <signal.h>
#include <memory>
#include <iostream>
int main() {
    std::shared_ptr<void(int)> handler(
        signal(SIGFPE, [](int signum) {throw std::logic_error("FPE"); }),
        [](__sighandler_t f) { signal(SIGFPE, f); });
    int i = 0;
    std::cin >> i;  // what if someone enters zero?
    try {
        i = 5/i;
    }
    catch (std::logic_error e) {
        std::cerr << e.what();
    }
}

这将设置一个新的信号处理程序,该处理程序会引发异常,并shared_ptr旧信号处理程序,并具有自定义的"删除"功能,当旧处理程序超出范围时,该功能会恢复旧处理程序。

您至少需要使用以下选项进行编译:

g++ -c Foo.cc -o Foo.o -fnon-call-exceptions -std=c++11

视觉C++还可以让你做类似的事情:

#include <eh.h>
#include <memory>
int main() {
    std::shared_ptr<void(unsigned, EXCEPTION_POINTERS*)> handler(
        _set_se_translator([](unsigned u, EXCEPTION_POINTERS* p) {
            switch(u) {
                case FLT_DIVIDE_BY_ZERO:
                case INT_DIVIDE_BY_ZERO:
                    throw std::logic_error("Divide by zero");
                    break;
                ...
                default:
                    throw std::logic_error("SEH exception");
            }
        }),
        [](_se_translator_function f) { _set_se_translator(f); });
    int i = 0;
    try {
        i = 5 / i;
    } catch(std::logic_error e) {
        std::cerr << e.what();
    }
}

当然,您可以跳过所有C++11左右的内容,并将它们放在传统的RAII管理结构中。

我所知C++规范没有提到除以零排除的任何内容。我相信你需要自己做...

Stroustrup在"C++的设计与演变"(Addison Wesley,1994(中说:"低级事件,如算术溢出和除以零,被假定由专用的低级机制处理,而不是由异常处理。这使C++能够在算术方面与其他语言的行为相匹配。它还避免了在大量流水线架构上发生的问题,在这些架构中,除以零等事件是异步的。`

您需要

使用关键字手动引发异常throw

例:

#include <iostream>
using namespace std;
double division(int a, int b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}
int main ()
{
   int x = 50;
   int y = 0;
   double z = 0;
   try {
     z = division(x, y);
     cout << z << endl;
   }catch (const char* msg) {
     cerr << msg << endl;
   }
   return 0;
}

您应该检查是否i = 0,然后不要除法。

(可选(在检查后,您可以抛出异常并稍后处理(。

更多信息: http://www.cprogramming.com/tutorial/exceptions.html

setjmp + longjmp

https://stackoverflow.com/a/25601100/895245 提到了从信号处理程序引发C++异常的可能性,但是从信号处理程序中抛出异常提到了几个警告,所以我会非常小心。

作为另一种潜在的危险可能性,您还可以尝试使用较旧的 C setjmp + longjmp 机制,如下所示: C 处理信号 SIGFPE 并继续执行

主.cpp

#include <csetjmp>
#include <csignal>
#include <cstring>
#include <iostream>
jmp_buf fpe;
void handler(int signum) {
    longjmp(fpe, 1);
}
int main() {
    volatile int i, j;
    for(i = 0; i < 10; i++) {
        struct sigaction act;
        struct sigaction oldact;
        memset(&act, 0, sizeof(act));
        act.sa_handler = handler;
        act.sa_flags = SA_NODEFER | SA_NOMASK;
        sigaction(SIGFPE, &act, &oldact);
        if (0 == setjmp(fpe)) {
            std::cout << "before divide" << std::endl;
            j = i / 0;
            sigaction(SIGFPE, &oldact, &act);
        } else {
            std::cout << "after longjmp" << std::endl;
            sigaction(SIGFPE, &oldact, &act);
        }
    }
    return 0;
}

编译并运行:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

输出:

i = 0
before divide
after longjmp
i = 1
before divide
after longjmp
i = 2
before divide
after longjmp

man longjmp说你可以从信号处理程序longjmp,但有一些警告:

POSIX.1-2008 技术勘误表 2 将 longjmp(( 和 siglongjmp(( 添加到异步信号安全函数列表中。 但是,该标准建议避免从信号处理程序使用这些函数,并继续指出如果这些函数是从信号处理程序调用的,该信号处理程序中断了对非异步信号安全函数的调用(或一些等效的函数,例如等效于 exit(3( 的步骤,这些步骤在从初始返回时发生调用 main(((,如果程序随后调用非异步信号安全函数,则行为未定义。 避免未定义行为的唯一方法是确保满足以下条件之一:

    从信号
  • 处理程序长跳后,程序不会调用任何非异步信号安全函数,也不会从对 main(( 的初始调用返回。

  • 每次调用非异步信号安全函数时,都必须阻止其处理程序执行长跳的任何信号,并且在从对 main(( 的初始调用返回后,不会调用任何非异步信号安全函数。

另请参阅:Longjmp 超出信号处理程序?

但是,从信号处理程序中抛出异常会提到这会对C++有进一步的危险:

但是,setjmp 和 longjmp 与异常和 RAII(ctors/dtor(不兼容。 :(您可能会因此而获得资源泄漏。

所以你也必须非常非常小心。

我想寓意是信号处理程序很难,除非你确切地知道自己在做什么,否则你应该尽可能避免它们。

检测浮点零除法

也可以通过 glibc 调用来检测浮点除以零:

#include <cfenv>
feenableexcept(FE_INVALID);

如所示:安静 NaN 和信令 NaN 有什么区别?

这使得它像整数除以零一样提高 SIGFPE,而不仅仅是静默地 qnan 和设置标志。

这个呢?用Clang测试,GCC投掷了SIGILL。

#include <iostream>
#include <cassert>
int main()
{
    unsigned int x = 42;
    unsigned int y = x;
    y -= x;
    x /= y;
    
    std::cout << x << " != "<< *(&x) << std::endl;
    assert (*(&x) == x);
}

如果你想捕获涉及整数的除以零错误(它对浮点数有效(,而不必通过抛出来触发它,你应该使用这样的信号处理程序:

void signalHandler( int signum ){
    //Division by 0 is c++ signal #8 (signum = 8).         
    cout << "Interrupt signal (" << signum << ") received.n";
    exit(signum);
}

然后将此函数定义为可能发生除以零的代码之前的信号处理程序,如下所示:

int main(){
   ...
   signal(SIGFPE, signalHandler);
   ...
}

请注意,SIGFPE 是无效算术运算的信号。

do i need to throw an exception or does the computer automatically throws one at runtime?

您需要自己throw异常并catch它。

try {
  //...
  throw int();
}
catch(int i) { }

或者catch代码引发的异常。

try {
    int *p = new int();
}
catch (std::bad_alloc e) {
    cerr << e.what();
}

在您的情况下,我不确定是否有任何用于除以零的标准例外。如果没有这样的例外,那么您可以使用,

catch(...) {  // catch 'any' exception
}

你可以只做assert(2 * i != i)这将抛出一个断言。如果您需要更花哨的东西,您可以编写自己的异常类。