捕获异常:除以零

时间:2011-05-25 08:32:51

标签: c++ exception-handling

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

int i = 0;

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

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

    cerr << e.what();
}

9 个答案:

答案 0 :(得分:52)

您需要自己检查并抛出异常。整数除以零在标准C ++中不是例外。

浮点数除以零,但至少具有处理它的特定方法。

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

你会认为overflow_error非常适合用零来表示。

但是5.6C++11部分,虽然我认为这不会改变上一次迭代),但明确指出:

  

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

因此,可以抛出该(或任何其他)异常。它还可以格式化您的硬盘并嘲笑: - )


如果您想要实现这样的野兽,可以在以下程序中使用intDivEx之类的内容:

#include <iostream>
#include <stdexcept>

// Integer division, 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;
}

int main (void) {
    int i = 42;

    try {
        i = intDivEx (10, 2);
    } catch (std::overflow_error e) {
        std::cout << e.what() << " -> ";
    }
    std::cout << i << std::endl;

    try {
        i = intDivEx (10, 0);
    } catch (std::overflow_error e) {
        std::cout << e.what() << " -> ";
    }
    std::cout << i << std::endl;

    return 0;
}

输出:

5
Divide by zero exception -> 5

你可以看到它抛出并捕获除以零的异常。


%等价物几乎完全相同:

// Integer remainder, catching divide by zero.

inline int intModEx (int numerator, int denominator) {
    if (denominator == 0)
        throw std::overflow_error("Divide by zero exception");
    return numerator % denominator;
}

答案 1 :(得分:14)

更新了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发送一个自定义删除&#39;当旧处理程序超出范围时恢复旧处理程序的函数。

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

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

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

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

答案 2 :(得分:7)

据我所知,C ++规范没有提及除零除外的任何内容。我相信你需要自己做...

  

Stroustrup在“C ++的设计和演变”(Addison Wesley,1994)中说,“低级事件,例如算术溢出和除以零,假设由专用的低级机制处理,而不是这使得C ++能够在算术运算时与其他语言的行为相匹配。它还避免了在流水线严重的体系结构中出现的问题,例如除以零的事件是异步的。“

答案 3 :(得分:1)

您应该检查是否i = 0而不是分开。

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

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

答案 4 :(得分:0)

您需要使用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;
}

答案 5 :(得分:0)

setjmp + longjmp

https://stackoverflow.com/a/25601100/895245提到了可能从信号处理程序中抛出C ++异常的可能性,但是Throwing an exception from within a signal handler提到了一些警告,因此我会非常小心。

作为另一种潜在的危险可能性,您也可以尝试使用较旧的C setjmp + longjmp机制,如下所示:C handle signal SIGFPE and continue execution

#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 out of signal handler?

然而,Throwing an exception from within a signal handler提到使用C ++会有进一步的危险:

  但是,

setjmp和longjmp与异常和RAII(ctors / dtors)不兼容。 :(您可能会因此而导致资源泄漏。

所以您也必须非常小心。

我想这是信号处理程序很难做到的道理,除非您确切知道自己在做什么,否则应尽量避免使用它们。

检测浮点零除

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

#include <cfenv>

feenableexcept(FE_INVALID);

What is difference between quiet NaN and signaling NaN?

所示

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

答案 6 :(得分:0)

这个呢?用 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);
}

答案 7 :(得分:-2)

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

您需要自己throw例外,catch它。 e.g。

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

catch您的代码抛出的异常。

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

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

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

答案 8 :(得分:-4)

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