Abfangende Ausnahme: Division durch Null

Lesezeit: 9 Minuten

Abfangende Ausnahme Division durch Null
Benutzer33424

Der folgende Code fängt keine Ausnahme ab, wenn ich versuche, durch 0 zu teilen. Muss ich eine Ausnahme auslösen, oder löst der Computer zur Laufzeit automatisch eine Ausnahme aus?

int i = 0;

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

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

    cerr << e.what();
}

  • Warum nicht einfach prüfen, ob i ist null oder nicht?

    – Nik

    25. Mai 2011 um 8:34 Uhr

  • Warum versuchst du zu dividieren durch ze— OH SCHEI-

    – BoltClock

    25. Mai 2011 um 8:35 Uhr

  • Nein, nicht wirklich, ich habe meine Frage bearbeitet.

    – Benutzer33424

    25. Mai 2011 um 8:40 Uhr

  • Diese Frage und alle darauffolgenden Antworten sind eine gute Studie über alles, was mit Ausnahmen nicht stimmt.

    – Carey Gregory

    15. Oktober 2013 um 4:15 Uhr

1646923808 275 Abfangende Ausnahme Division durch Null
paxdiablo

Sie müssen es selbst überprüfen und eine Ausnahme auslösen. Ganzzahlige Division durch Null ist keine Ausnahme in Standard-C++.

Es ist auch keine Gleitkommadivision durch Null, aber zumindest hat das spezielle Mittel, um damit umzugehen.

Die in der ISO-Norm aufgeführten Ausnahmen sind:

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

und das könnte man auch ziemlich überzeugend argumentieren overflow_error (Die von IEEE754-Gleitkomma erzeugte Unendlichkeit könnte als Überlauf angesehen werden) oder domain_error (es ist ein Problem mit dem Eingabewert) wäre ideal, um eine Division durch Null anzuzeigen.

Abschnitt jedoch 5.6 (von C++11obwohl ich nicht glaube, dass sich dies gegenüber der vorherigen Iteration geändert hat) heißt es ausdrücklich:

Wenn der zweite Operand von / oder % Null ist, ist das Verhalten undefiniert.

So dass es könnten Werfen Sie diese (oder andere) Ausnahmen aus. Es könnte auch Ihre Festplatte formatieren und höhnisch lachen 🙂


Wenn Sie ein solches Biest implementieren wollten, könnten Sie so etwas wie verwenden intDivEx im folgenden Programm (unter Verwendung der Überlaufvariante):

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

Dies gibt aus:

Divide by zero exception -> 42
5

und Sie können sehen, dass es die Ausnahme für den Fall der Division durch Null auslöst und abfängt (wobei die Rückgabevariable unberührt bleibt).


Die % Äquivalent ist fast genau dasselbe:

  • Danke, ich habe nach Fällen gesucht, in denen das System eine Ausnahme auslöst. Sind diese in C++ möglich?

    – Benutzer33424

    25. Mai 2011 um 8:43 Uhr

  • @ user33424, ja, es ist möglich, siehe meine Antwort zum Beispiel auf std::bad_alloc die vorbei geworfen wird new

    – iammilind

    25. Mai 2011 um 8:46 Uhr


  • hmm, Sie müssen also nicht sorgfältig auswählen, wenn Sie sich für die Verwendung einer Ausnahmeklasse entscheiden, da alle denselben Parameter verwenden.

    – Benutzer33424

    25. Mai 2011 um 9:09 Uhr

  • @user Aber Sie sollten eine auswählen, die Ihre Absicht klar macht, was schief gelaufen ist. Das Werfen von bad_alloc für zB invalid_argument ist schlechtes Design.

    – RedX

    25. Mai 2011 um 9:40 Uhr

  • Da die Domäne einer Funktion die Menge gültiger Eingaben ist, für die die Funktion definiert ist, würde dies nicht der Fall sein domain_error besser geeignet sein, hier zu werfen?

    – Johannes H

    14. März 2014 um 18:21 Uhr

1646923809 112 Abfangende Ausnahme Division durch Null
Tom

Aktualisiert mit Kommentaren von ExcessPhase

Mit GCC (mindestens Version 4.8) können Sie dieses Verhalten emulieren:

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

Dies richtet einen neuen Signalhandler ein, der eine Ausnahme auslöst, und a shared_ptr zum alten Signal-Handler, mit einer benutzerdefinierten ‘Lösch’-Funktion, die den alten Handler wiederherstellt, wenn er den Gültigkeitsbereich verlässt.

Sie müssen mindestens mit diesen Optionen kompilieren:

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

Mit Visual C++ können Sie auch etwas Ähnliches tun:

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

Und natürlich können Sie den ganzen C++11-Stil überspringen und sie in eine traditionelle RAII-Verwaltungsstruktur stecken.

  • Das Setzen und Wiederherstellen des Signalhandlers sollte RAII verwenden! Sie können auch nicht davon ausgehen, dass der Standard-Signalhandler derjenige war, den Sie vorübergehend ersetzt haben.

    Benutzer4590120

    3. November 2015 um 17:46 Uhr

  • Und warum verwendest du shared_ptr? Wie ich verstehe, Handler mit RAII zu deinstallieren. Aber wenn Sie diesen Zeiger nicht teilen, sollte es besser sein, unique_ptr oder sogar eine benutzerdefinierte RAII-Struktur zu verwenden.

    – kyb

    8. Juni 2018 um 14:56 Uhr

  • Sind FLT_DIVIDE_BY_ZERO und SIGFPE nicht nur für Gleitkommaausnahmen?

    – DodgyCodeException

    21. Dezember 2018 um 15:04 Uhr

  • @Tom ja, es geht darum, die Division durch Null abzufangen, aber was ich zu sagen versuche, ist, dass ich dachte, FLT_DIVIDE_BY_ZERO und SIGFPE seien nur für Gleitkommaausnahmen und dachten das nicht ganze Zahl Division durch Null (das Thema dieses Beitrags) könnte von einem dieser beiden Signale abgefangen werden.

    – DodgyCodeException

    5. Januar 2019 um 17:00 Uhr

  • Ah, ich verstehe, was du meinst, aber nein, SIGFPE ist auch für ganzzahlige Division durch Null. Single Unix Specification definiert SIGFPE als „Fehlerhafte arithmetische Operation“. FLT_DIVIDE_BY_ZERO ist in der Tat nur für Fließkommazahlen und es gibt ein entsprechendes INT_DIVIDE_BY_ZERO; Ich werde die Antwort aktualisieren.

    – Tom

    8. Januar 2019 um 15:06 Uhr

1646923809 2 Abfangende Ausnahme Division durch Null
Mayank

Soweit ich weiß, erwähnen die C++-Spezifikationen nichts über die Ausnahme der Division durch Null. ich glaub das musst du selber machen…

Stroustrup sagt in “The Design and Evolution of C++” (Addison Wesley, 1994): “Low-Level-Ereignisse wie arithmetische Überläufe und Division durch Null werden eher von einem dedizierten Mechanismus auf niedrigerer Ebene als von Ausnahmen behandelt . Dies ermöglicht es C++, sich dem Verhalten anderer Sprachen in Bezug auf Arithmetik anzupassen. Es vermeidet auch die Probleme, die bei stark Pipeline-Architekturen auftreten, bei denen Ereignisse wie das Teilen durch Null asynchron sind.”`

Sie müssen die Ausnahme manuell mit auslösen throw Stichwort.

Beispiel:

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

1646923810 940 Abfangende Ausnahme Division durch Null
Wadiklk

Sie sollten prüfen, ob i = 0 und dann nicht teilen.

(Optional können Sie nach der Überprüfung eine Ausnahme auslösen und später behandeln).

Mehr Infos unter: http://www.cprogramming.com/tutorial/exceptions.html

setjmp + longjmp

https://stackoverflow.com/a/25601100/895245 erwähnte die Möglichkeit oder das Auslösen einer C++-Ausnahme von einem Signalhandler, aber das Auslösen einer Ausnahme innerhalb eines Signalhandlers erwähnt mehrere Vorbehalte, also wäre ich sehr vorsichtig.

Als weitere potenziell gefährliche Möglichkeit können Sie auch versuchen, das ältere C setjmp + longjmp Mechanismus wie unter: C handhaben Sie das Signal SIGFPE und setzen Sie die Ausführung fort

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

Kompilieren und ausführen:

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

Ausgabe:

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

man longjmp sagt, dass Sie können longjmp von Signalhandlern, aber mit einigen Einschränkungen:

POSIX.1-2008 Technical Corrigendum 2 fügt longjmp() und siglongjmp() zur Liste der Async-Signal-sicheren Funktionen hinzu. Der Standard empfiehlt jedoch, die Verwendung dieser Funktionen von Signal-Handlern zu vermeiden, und weist weiter darauf hin, dass, wenn diese Funktionen von einem Signal-Handler aufgerufen werden, der einen Aufruf einer nicht-asynchronen signalsicheren Funktion (oder eines Äquivalents, wie z wie die zu exit(3) äquivalenten Schritte, die bei einer Rückkehr vom ursprünglichen Aufruf von main() auftreten), ist das Verhalten undefiniert, wenn das Programm anschließend eine nicht asynchronsignalsichere Funktion aufruft. Die einzige Möglichkeit, undefiniertes Verhalten zu vermeiden, besteht darin, Folgendes sicherzustellen:

  • Nach einem langen Sprung vom Signal-Handler ruft das Programm keine nicht-async-signalsicheren Funktionen auf und kehrt nicht vom ursprünglichen Aufruf zu main() zurück.

  • Jedes Signal, dessen Handler einen langen Sprung durchführt, muss bei jedem Aufruf einer nicht-asynchronen signalsicheren Funktion blockiert werden, und es werden keine nicht-asynchronen signalsicheren Funktionen nach der Rückkehr vom ursprünglichen Aufruf von main() aufgerufen.

Siehe auch: Longjmp aus Signalhandler?

Das Auslösen einer Ausnahme innerhalb eines Signalhandlers erwähnt jedoch, dass dies weitere Gefahren mit C++ birgt:

setjmp und longjmp sind jedoch nicht mit Ausnahmen und RAII (ctors/dtors) kompatibel. 🙁 Sie werden wahrscheinlich Ressourcenlecks bekommen.

also müsstest du auch damit sehr sehr vorsichtig sein.

Ich denke, die Moral ist, dass Signalhandler hart sind, und Sie sollten sie so weit wie möglich vermeiden, es sei denn, Sie wissen genau, was Sie tun.

Gleitkomma-Nulldivision erkennen

Es ist auch möglich, eine Gleitkommadivision durch Null mit einem glibc-Aufruf zu erkennen:

#include <cfenv>

feenableexcept(FE_INVALID);

wie gezeigt unter: Was ist der Unterschied zwischen stillem NaN und signalisierendem NaN?

Dadurch wird SIGFPE ebenso wie die Integer-Division durch Null erhöht, anstatt nur stillschweigend zu qnan und Flags zu setzen.

1646923810 21 Abfangende Ausnahme Division durch Null
Damkrat

Was ist mit diesem? Mit Clang getestet, wirft 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);
}

988250cookie-checkAbfangende Ausnahme: Division durch Null

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy