簡體   English   中英

捕獲異常:除以零

[英]Catching exception: divide by zero

當我嘗試除以 0 時,以下代碼未捕獲異常。我需要拋出異常,還是計算機在運行時自動拋出異常?

int i = 0;

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

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

    cerr << e.what();
}

您需要自己檢查並拋出異常。 Integer 除以零在標准 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 (由 IEEE754 浮點生成的無窮大可能被視為溢出)或domain_error (這輸入值的問題)對於指示除以零是理想的。

但是,第5.6節( C++11 ,盡管我認為這與之前的迭代沒有改變)特別指出:

如果/%的第二個操作數為零,則行為未定義。

因此,它可能會拋出那些(或任何其他)異常。 它還可以格式化您的硬盤並嘲笑:-)


如果你想實現這樣的野獸,你可以在下面的程序中使用類似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 ,並使用自定義“刪除”function 在舊處理程序退出 scope 時恢復它。

您至少需要使用以下選項進行編譯:

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 風格,將它們放入傳統的 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;
}

setjmp + longjmp

https://stackoverflow.com/a/25601100/895245提到了從信號處理程序中拋出 C++ 異常的可能性,但是從信號處理程序中拋出異常提到了幾個警告,所以我會非常小心。

作為另一種潛在的危險可能性,您也可以嘗試使用較舊的 C setjmp + longjmp機制,如: C 處理信號 SIGFPE 並繼續執行

主文件

#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

Output:

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

man longjmp說您可以從信號處理程序中進行longjmp ,但有一些警告:

POSIX.1-2008 Technical Corrigendum 2 將 longjmp() 和 siglongjmp() 添加到異步信號安全函數列表中。 但是,該標准建議避免在信號處理程序中使用這些函數,並繼續指出,如果這些函數是從中斷對非異步信號安全 function 的調用的信號處理程序調用的(或一些等價物,例如與從對 main()) 的初始調用返回時發生的等效於 exit(3) 的步驟相同,如果程序隨后調用非異步信號安全的 function,則行為未定義。 避免未定義行為的唯一方法是確保以下之一:

  • 從信號處理程序長跳轉后,程序不會調用任何非異步信號安全函數,也不會從對 main() 的初始調用返回。

  • 在每次調用非異步信號安全 function 時,必須阻止處理程序執行長跳轉的任何信號,並且在從初始調用返回 main() 后不會調用非異步信號安全函數。

另請參閱: Longjmp 超出信號處理程序?

然而,從信號處理程序中拋出異常提到這對 C++ 有進一步的危險:

但是,setjmp 和 longjmp 與異常和 RAII(ctors/dtors)不兼容。 :( 你可能會因此而得到資源泄漏。

所以你也必須非常小心。

我想道德是信號處理程序很難,除非您確切知道自己在做什么,否則您應該盡可能避免使用它們。

檢測浮點零除法

也可以通過 glibc 調用來檢測浮點除以零:

#include <cfenv>

feenableexcept(FE_INVALID);

如圖所示: 安靜NaN和信令NaN有什么區別?

這使得它提高 SIGFPE 以及 integer 除以零而不是靜默 qnan 和設置標志。

您應該檢查i = 0並且不除。

(可選地,在檢查它之后,您可以拋出異常並稍后處理)。

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

這個如何? 用 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?

要么您需要自己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)這將拋出一個斷言。 如果您需要更高級的東西,您可以編寫自己的異常 class。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM