Hallo zusammen,
ich möchte Integer-overflows mittels Exceptions behandeln. Um ein
Beispiel zu geben:
Es sollen zwei Zahlen multipliziert werden, diese beiden Zahlen werden
mittels cin in zwei Integer-Variablen a und b eingelesen, das Ergebnis
in die Variable c. Wenn eine Eingabe dazu führt, dass eine Variable
überläuft soll es mittels Ausnahme erkannt werden und eine Fehlermeldung
ausgegeben werden.
Wie könnte das als Code aussehen?
In Ordnung, kann man sonst wie erkennen wann eine Variable überläuft?
Vielleicht irgendwie in Verbindung mit narrow_cast? Ich könnte ja die
Variablen als double-typen deklarieren und dann eine Konvertierung in
int mittels narrow_cast vornehmen. Oder bin ich da auf dem Holzweg.
edelhannes schrieb:> In Ordnung, kann man sonst wie erkennen wann eine Variable überläuft?
rechnet mit bigint, wenn der Ergebnis > max_int ist dann würde es
überlaufen.
Google zeigt diverse Resultate wie man eine Multiplikation auf Überlauf
prüft. Es gibt keine direkte/einfache Methode. Nimm eine der zu
findenden Varianten und füge den Exception-Code hinzu.
narrow_cast gibts im C++ Standard nicht. Nach double konvertieren ist
immer gefährlich, da kann man ggf. Präzision verlieren.
Stefan S. schrieb:> Muss die Erklärung denn in Deutsch sein?
nein natürlich nicht :-)
Danke für den Link
Peter II schrieb:> rechnet mit bigint, wenn der Ergebnis > max_int ist dann würde es> überlaufen.
Das ist ja schon fast zu einfach.
edelhannes schrieb:> Das ist eigentlich die Info die ich brauche, das wusste ich nicht.
Die einfachste Lösung wird sein, das du keine Werte eingibst die einen
Überlauf erzeugen, damit nimmst du dem Computer auch noch Denkarbeit ab,
das kann der Mensch ohnehin besser.
Scott Meyers schrieb:> Schreib Dir ne eigene Klasse, die das macht.
Ich glaub darauf wär er schon gekommen. Nur weil man etwas in eine
Klasse verpackt, kann es nicht plötzlich kniffelige Berechnungen
durchführen. Die Frage war, wie man Overflows erkennt, was
portabel/standardkonform nicht offensichtlich ist.
Dr. Sommer schrieb:> Scott Meyers schrieb:>> Schreib Dir ne eigene Klasse, die das macht.> Ich glaub darauf wär er schon gekommen. Nur weil man etwas in eine> Klasse verpackt, kann es nicht plötzlich kniffelige Berechnungen> durchführen.
das war auch nicht die Behauptung. Trotzdem ist es möglich!
> Die Frage war, wie man Overflows erkennt, was> portabel/standardkonform nicht offensichtlich ist.
edelhannes schrieb:> Um den Status des overflow-Flags zu sehen? Keine Ahnung wie man das> macht..
Also z.B. beim ARMv7M wird das Carry Flag überhaupt nicht bei der
Multiplikation gesetzt. Das bringts also nicht so wirklich...
edelhannes schrieb:> Cyberpunk schrieb:>> Ist Inline-Assembler keine zulässige Lösung?>> Um den Status des overflow-Flags zu sehen? Keine Ahnung wie man das> macht..
Je nach Architektur gibt's in Assembler ggf. gar keinen Overflow bei
Multiplikationen, weil das Ergebnis nicht auf die gleiche Größe
gezwungen wird, wie die Operanden. So ist es beispielsweise bei x86 und
beim AVR.
Ich habe schnell eine Klasse dafür erstellt, ist im Anhang zu finden. Es
können sich durchaus noch kleinere Fehler darin befinden, und es findet
damit keine Integer Promotion statt, scheint aber mehr oder weniger zu
funktionieren.
Daniel A. schrieb:> Es> können sich durchaus noch kleinere Fehler darin befinden
Leider ja:
1. Der Overflow von signed Zahlen ist undefiniertes Verhalten, somit
kann z.B. der if-Block im + Operator wegoptimiert werden und die
Exception wird ggf. nie ausgelöst. Das lässt sich reparieren, indem man
vor der Operation den Bereich prüft.
std::numeric_limits<T>::max()/min() hilft dabei.
2. Statt der C-Casts (T)... würde ich lieber static_cast<T> (...)
nutzen.
3. Ich würde mit static_assert sicherstellen ob T/U überhaupt Zahlen
sind.
4.
1
template<typenameU>
2
OverflowCheckingNumber(Uy){
3
if(y!=(U)(T)y)
Das greift leider nicht (immer). Wenn man z.B. Signed->unsigned->signed
castet, kann sehr wohl das gleiche rauskommen, obwohl die Zahl nicht als
"unsigned" darstellbar ist. Hier braucht man Overloads um alle
Spezialfälle abzudecken...
5.
1
template<typenameU>operatorU()const;
Das halte ich für ziemlich gefährlich - ein Typ der in alles umwandelbar
ist! Da würde ich lieber eine Funktion für nehmen.
6. Man könnte noch großzügig "constexpr" verteilen.
7. Ich würde noch Vergleichsoperatoren sowie Copy/Move-Konstruktoren
einbauen.
Dr. Sommer schrieb:> 5.template<typename U> operator U() const;Das halte ich für ziemlich> gefährlich - ein Typ der in alles umwandelbar> ist!
Nö, nur in das, wohin auch T implizit konvertiert werden kann.
Rolf M. schrieb:> Nö, nur in das, wohin auch T implizit konvertiert werden kann.
Achja, okay weil bei der Rückgabe kein cast ist. Man könnte es noch
etwas aufhübschen:
Hier nochmal eine neue Version, mit ein paar Bugfixes. Sorry wegen dem
letzten von mir gelöschten Beitrag, mir ist im letzten Moment noch ein
Fehler aufgefallen.
Daniel A. schrieb:> Hier nochmal eine neue Version
Wow ich bin eigentlich noch nicht so fortgeschritten, dass ich im ganzen
verstehe was da gemacht wird. Danke trotzdem für deine Mühe, ich werde
wohl etwas simpleres ausprobieren müssen. Ich habe erst die Grundlagen
von C++ durch, d.h. alles bis simple Klassen.
Mal einfach ausgedrückt: Diese Überlauf-Erkennungs-Operationen "sehen"
für den Compiler teilweise sinnlos aus und sie werden mitunter
wegoptimiert. Da gibt es endlose Feinheiten zu beachten. Ganz viele
Sicherheitslücken in Software basieren exakt darauf, dass jemand
Überläufe nicht oder nicht korrekt abfängt. Da könnte man ganze Bücher
drüber schreiben.
In Bereichen, wo Softwarefehler Menschen verletzen oder gar töten
könnten (Flugzeug, ...) werden deshalb gerne Sprachen verwendet, die
Überlauf von Haus aus erkennen und behandeln. C kommt natürlich auch
vor, aber der Output vom Compiler wird intensiv getestet.
Siehe auch hier für einen kleinen Einstieg:
http://www.fefe.de/intof.html
Jochen schrieb:> Ganz viele> Sicherheitslücken in Software basieren exakt darauf, dass jemand> Überläufe nicht oder nicht korrekt abfängt.
dabei geht es aber um Speicherüberläufe, das hat nur indirekt etwas
damit zu tun.
Peter II schrieb:> dabei geht es aber um Speicherüberläufe, das hat nur indirekt etwas> damit zu tun.
Das eine hat nicht immer, aber oft direkt mit dem anderen zu tun. Im
Link oben ist ein schönes Beispiel aus einer älteren Version von
libiberty:
1
if (*p == '*')
2
{
3
++p;
4
total_width += abs (va_arg (ap, int));
5
}
total_width könnte überlaufen, da keine Prüfung implementiert ist. Nach
Addition eines positiven Wertes wird total_width also kleiner. Als folge
wird zu wenig Speicher reserviert -> Speicherüberlauf.
Außerdem kann abs(...) auch negativ werden, mit gleicher Folge wie oben.
Dazu braucht man nur INT_MIN als Argument übergeben, was im obigen
Beispiel durch Benutzereingabe möglich ist.
Jochen schrieb:> Diese Überlauf-Erkennungs-Operationen "sehen"> für den Compiler teilweise sinnlos aus und sie werden mitunter> wegoptimiert.
Das ist aber dann ein Fehler im Compiler.
Der Compiler darf sie natürlich wegoptimieren, wenn sie tatsächlich
sinnlos sind, denn dann tritt ja auch kein Überlauf auf. Aber das muss
er auch garantieren.
Es gab ähnliche Diskussionen, als GCC angefangen hat, scharf zu
optimieren und die Tests gegen Pufferüberläufe wegzuoptimieren. Das
haben die ganz schnell wieder sein gelassen...
Daniel A. schrieb:> Hier nochmal eine neue Version, mit ein paar Bugfixes. Sorry wegen dem> letzten von mir gelöschten Beitrag, mir ist im letzten Moment noch ein> Fehler aufgefallen.
Deine Klasse krankt an genau dem o.g. Problem: Du führst einfach die
Berechnung aus und guckst hinterher, ob das Ergebnis korrekt ist, indem
du zurückrechnest.
Ein Überlauf bei signed int ist aber nicht nur unschön, sondern auch
undefined behaviour! D.h., der Compiler dürfte das alles wegoptimieren
und durch exit(0) ersetzen...
Mehr dazu:
https://www.securecoding.cert.org/confluence/display/c/INT31-C.+Ensure+that+integer+conversions+do+not+result+in+lost+or+misinterpreted+data