简体   繁体   English

捕获异常:除以零

[英]Catching exception: divide by zero

The following code does not catch an exception, when I try to divide by 0. Do I need to throw an exception, or does the computer automatically throw one at runtime?当我尝试除以 0 时,以下代码未捕获异常。我需要抛出异常,还是计算机在运行时自动抛出异常?

int i = 0;

cin >> i;  // what if someone enters zero?

try {
    i = 5/i;
}
catch (std::logic_error e) {

    cerr << e.what();
}

You need to check it yourself and throw an exception.您需要自己检查并抛出异常。 Integer divide by zero is not an exception in standard C++. Integer 除以零在标准 C++ 中也不例外。

Neither is floating point divide by zero but at least that has specific means for dealing with it.浮点除以零也不是,但至少它有处理它的特定方法。

The exceptions listed in the ISO standard are: ISO 标准中列出的例外情况是:

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;
}

and you could argue quite cogently that either overflow_error (the infinity generated by IEEE754 floating point could be considered overflow) or domain_error (it is a problem with the input value) would be ideal for indicating a divide by zero.您可以非常有说服力地争辩说, overflow_error (由 IEEE754 浮点生成的无穷大可能被视为溢出)或domain_error (这输入值的问题)对于指示除以零是理想的。

However, section 5.6 (of C++11 , though I don't think this has changed from the previous iteration) specifically states:但是,第5.6节( C++11 ,尽管我认为这与之前的迭代没有改变)特别指出:

If the second operand of / or % is zero, the behavior is undefined.如果/%的第二个操作数为零,则行为未定义。

So, it could throw those (or any other) exceptions.因此,它可能会抛出那些(或任何其他)异常。 It could also format your hard disk and laugh derisively:-)它还可以格式化您的硬盘并嘲笑:-)


If you wanted to implement such a beast, you could use something like intDivEx in the following program (using the overflow variant):如果你想实现这样的野兽,你可以在下面的程序中使用类似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;
}

This outputs:这输出:

Divide by zero exception -> 42
5

and you can see it throws and catches the exception (leaving the return variable untouched) for the divide by zero case.你可以看到它抛出并捕获了除以零的异常(保持返回变量不变)。


The % equivalent is almost exactly the same: %当量几乎完全相同:

Updated with comments from ExcessPhase更新了来自 ExcessPhase 的评论

GCC (at least version 4.8) will let you emulate this behaviour: 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();
    }
}

This sets up a new signal handler which throws an exception, and a shared_ptr to the old signal handler, with a custom 'deletion' function that restores the old handler when it goes out of scope.这将设置一个引发异常的新信号处理程序,并为旧信号处理程序设置一个shared_ptr ,并使用自定义“删除”function 在旧处理程序退出 scope 时恢复它。

You need to compile with at least these options:您至少需要使用以下选项进行编译:

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

Visual C++ will also let you do something similar: Visual 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();
    }
}

And of course you can skip all the C++11-ishness of this and put them in a traditional RAII-managing struct.当然,您可以跳过所有 C++11 风格,将它们放入传统的 RAII 管理结构中。

As far as I know C++ specifications does not mention anything about divide by zero exeption.据我所知,C++ 规范没有提到除以零例外的任何内容。 I believe you need to do it yourself...我相信你需要自己做...

Stroustrup says, in "The Design and Evolution of C++" (Addison Wesley, 1994), "low-level events, such as arithmetic overflows and divide by zero, are assumed to be handled by a dedicated lower-level mechanism rather than by exceptions. This enables C++ to match the behaviour of other languages when it comes to arithmetic. It also avoids the problems that occur on heavily pipelined architectures where events such as divide by zero are asynchronous."` Stroustrup 说,在“C++ 的设计和演变”(Addison Wesley,1994 年)中,“假设算术溢出和除以零等低级事件由专用的低级机制处理,而不是由异常处理. 这使得 C++ 在算术方面能够与其他语言的行为相匹配。它还避免了在诸如除以零之类的事件是异步的重流水线架构上发生的问题。”`

You need to throw the exception manually using throw keyword.您需要使用throw关键字手动抛出异常。

Example:例子:

#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;
}

setjmp + longjmp setjmp + longjmp

https://stackoverflow.com/a/25601100/895245 mentioned the possibility or throwing a C++ exception from a signal handler, but Throwing an exception from within a signal handler mentions several caveats of that, so I would be very careful. https://stackoverflow.com/a/25601100/895245提到了从信号处理程序中抛出 C++ 异常的可能性,但是从信号处理程序中抛出异常提到了几个警告,所以我会非常小心。

As another potentially dangerous possibility, you can also try to use the older C setjmp + longjmp mechanism as shown at: C handle signal SIGFPE and continue execution作为另一种潜在的危险可能性,您也可以尝试使用较旧的 C setjmp + longjmp机制,如: C 处理信号 SIGFPE 并继续执行

main.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;
}

Compile and run:编译并运行:

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

Output: Output:

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

man longjmp says that you can longjmp from signal handlers, but with a few caveats: man longjmp说您可以从信号处理程序中进行longjmp ,但有一些警告:

POSIX.1-2008 Technical Corrigendum 2 adds longjmp() and siglongjmp() to the list of async-signal-safe functions. POSIX.1-2008 Technical Corrigendum 2 将 longjmp() 和 siglongjmp() 添加到异步信号安全函数列表中。 However, the standard recommends avoiding the use of these functions from signal handlers and goes on to point out that if these functions are called from a signal handler that interrupted a call to a non-async-signal-safe function (or some equivalent, such as the steps equivalent to exit(3) that occur upon a return from the initial call to main()), the behavior is undefined if the program subsequently makes a call to a non-async-signal-safe function.但是,该标准建议避免在信号处理程序中使用这些函数,并继续指出,如果这些函数是从中断对非异步信号安全 function 的调用的信号处理程序调用的(或一些等价物,例如与从对 main()) 的初始调用返回时发生的等效于 exit(3) 的步骤相同,如果程序随后调用非异步信号安全的 function,则行为未定义。 The only way of avoiding undefined behavior is to ensure one of the following:避免未定义行为的唯一方法是确保以下之一:

  • After long jumping from the signal handler, the program does not call any non-async-signal-safe functions and does not return from the initial call to main().从信号处理程序长跳转后,程序不会调用任何非异步信号安全函数,也不会从对 main() 的初始调用返回。

  • Any signal whose handler performs a long jump must be blocked during every call to a non-async-signal-safe function and no non-async-signal-safe functions are called after returning from the initial call to main().在每次调用非异步信号安全 function 时,必须阻止处理程序执行长跳转的任何信号,并且在从初始调用返回 main() 后不会调用非异步信号安全函数。

See also: Longjmp out of signal handler?另请参阅: Longjmp 超出信号处理程序?

However Throwing an exception from within a signal handler mentions that this has further dangers with C++:然而,从信号处理程序中抛出异常提到这对 C++ 有进一步的危险:

setjmp and longjmp aren't compatible with exceptions and RAII (ctors/dtors), though.但是,setjmp 和 longjmp 与异常和 RAII(ctors/dtors)不兼容。 :( You'll probably get resource leaks with this. :( 你可能会因此而得到资源泄漏。

so you would have to be very very careful with that as well.所以你也必须非常小心。

I guess the moral is that signal handlers are hard, and you should avoid them as much as possible unless you know exactly what you are doing.我想道德是信号处理程序很难,除非您确切知道自己在做什么,否则您应该尽可能避免使用它们。

Detect floating point zero division检测浮点零除法

It is also possible to detect floating point division by zero with a glibc call to:也可以通过 glibc 调用来检测浮点除以零:

#include <cfenv>

feenableexcept(FE_INVALID);

as shown at: What is the difference between quiet NaN and signaling NaN?如图所示: 安静NaN和信令NaN有什么区别?

This makes it raises SIGFPE as well like the integer division by zero instead of just silently qnan and setting flags.这使得它提高 SIGFPE 以及 integer 除以零而不是静默 qnan 和设置标志。

You should check if i = 0 and not divide then.您应该检查i = 0并且不除。

(Optionally after checking it you can throw an exception and handle it later). (可选地,在检查它之后,您可以抛出异常并稍后处理)。

More info at: http://www.cprogramming.com/tutorial/exceptions.html更多信息请访问: http://www.cprogramming.com/tutorial/exceptions.html

What about this one?这个如何? Tested with Clang, GCC throws SIGILL.用 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);
}

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

Either you need to throw the exception yourself and catch it.要么您需要自己throw异常并catch它。 eg例如

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

Or catch the exception which is thrown by your code.或者catch代码抛出的异常。

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

In your case, I am not sure if is there any standard exception meant for divide by zero.在您的情况下,我不确定是否有任何标准异常用于除以零。 If there is no such exception then you can use,如果没有这样的例外,那么你可以使用,

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

You can just do assert(2 * i != i) which will throw an assert.你可以做assert(2 * i != i)这将抛出一个断言。 You can write your own exception class if you need something fancier.如果您需要更高级的东西,您可以编写自己的异常 class。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM