web-dev-qa-db-ja.com

例外をキャッチ:ゼロ除算

0で除算しようとすると、次のコードは例外をキャッチしません。例外をスローする必要がありますか、それとも実行時にコンピューターが自動的に例外をスローしますか?

int i = 0;

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

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

    cerr << e.what();
}
34
user33424

自分で確認して例外をスローする必要があります。ゼロによる整数除算は、標準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(それはis入力値の問題)ゼロ除算を示すのに理想的です。

ただし、セクション5.6C++11の、これは前の反復から変更されたとは思わないが)は具体的に次のように述べている。

/または%の第2オペランドがゼロの場合、動作は未定義です。

したがって、それはcouldそれら(またはその他の)例外をスローします。また、ハードディスクをフォーマットして、ばかに笑うこともあります:-)


そのような獣を実装したい場合は、次のプログラムで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;
}
57
paxdiablo

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

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管理構造体に入れることができます。

18
Tom

私の知る限り、C++仕様では、ゼロ除算例外については何も言及していません。自分でやる必要があると思う...

Stroustrupは、「C++の設計と進化」(Addison Wesley、1994)で、「算術オーバーフローやゼロ除算などの低レベルイベントは、例外ではなく専用の低レベルメカニズムによって処理されると想定されています。これにより、C++は、算術に関して他の言語の動作と一致することができます。また、ゼロ除算などのイベントが非同期であるパイプラインの多いアーキテクチャで発生する問題を回避できます。

7
Mayank

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

i = 0そして分割しません。

(オプションで、チェックした後、例外をスローして後で処理できます)。

詳細情報: http://www.cprogramming.com/tutorial/exceptions.html

1
Vadiklk

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

出力:

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()を追加します。ただし、標準では、シグナルハンドラからのこれらの関数の使用を避けることを推奨し、これらの関数が非同期シグナルセーフでない関数(またはそれと同等のものなど)の呼び出しを中断したシグナルハンドラから呼び出された場合、 main()への最初の呼び出しから戻るときに発生するexit(3)と同等のステップとして、プログラムがその後非同期シグナルセーフでない関数を呼び出した場合の動作は未定義です。未定義の動作を回避する唯一の方法は、次のいずれかを確認することです。

  • シグナルハンドラから長いジャンプの後、プログラムは非同期シグナルセーフでない関数を呼び出さず、main()の最初の呼び出しから戻りません。

  • ハンドラーがロングジャンプを実行するシグナルは、非同期シグナルセーフでない関数を呼び出すたびにブロックする必要があり、main()の最初の呼び出しから戻った後、非同期シグナルセーフでない関数は呼び出されません。

参照: Longjmp out of signal handler?

ただし、 シグナルハンドラ内から例外をスローする は、これにはC++のさらなる危険性があると述べています。

ただし、setjmpとlongjmpは、例外およびRAII(ctor/dtor)と互換性がありません。 :(おそらくこれでリソースリークが発生します。

そのため、同様に非常に注意する必要があります。

私は、シグナルハンドラは難しいという教訓があると思います。あなたが何をしているかを正確に知らない限り、シグナルハンドラはできる限り避けるべきです。

浮動小数点ゼロ除算を検出

Glibc呼び出しでゼロによる浮動小数点除算を検出することもできます。

#include <cfenv>

feenableexcept(FE_INVALID);

quiet NaNとシグナルNaNの違いは何ですか?

これにより、単に静かにqnanしてフラグを設定する代わりに、ゼロによる整数除算と同様にSIGFPEが発生します。