Auf den ersten Blick scheint diese Frage ein Duplikat von Wie erkennt man einen Ganzzahlüberlauf? es ist jedoch tatsächlich deutlich anders.
Ich habe festgestellt, dass das Erkennen eines vorzeichenlosen Ganzzahlüberlaufs zwar ziemlich trivial ist, das Erkennen eines vorzeichenbehafteten Überlaufs in C / C ++ jedoch schwieriger ist, als die meisten Leute denken.
Der naheliegendste und doch naivste Weg, dies zu tun, wäre etwa:
int add(int lhs, int rhs)
{
int sum = lhs + rhs;
if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) {
/* an overflow has occurred */
abort();
}
return sum;
}
Das Problem dabei ist, dass gemäß dem C-Standard ein vorzeichenbehafteter Ganzzahlüberlauf ein undefiniertes Verhalten ist. Mit anderen Worten, gemäß dem Standard ist Ihr Programm genauso ungültig, als ob Sie einen Nullzeiger dereferenziert hätten, sobald Sie überhaupt einen signierten Überlauf verursachen. Sie können also kein undefiniertes Verhalten verursachen und dann versuchen, den Überlauf nachträglich zu erkennen, wie im obigen Beispiel für die Überprüfung nach der Bedingung.
Obwohl die obige Prüfung wahrscheinlich bei vielen Compilern funktioniert, können Sie sich nicht darauf verlassen. Da der C-Standard besagt, dass ein vorzeichenbehafteter Ganzzahlüberlauf undefiniert ist, optimieren einige Compiler (wie GCC) die obige Prüfung, wenn Optimierungsflags gesetzt sind, da der Compiler davon ausgeht, dass ein vorzeichenbehafteter Überlauf unmöglich ist. Dies unterbricht den Versuch, auf Überlauf zu prüfen, vollständig.
Eine andere Möglichkeit, den Überlauf zu überprüfen, wäre:
int add(int lhs, int rhs)
{
if (lhs >= 0 && rhs >= 0) {
if (INT_MAX - lhs <= rhs) {
/* overflow has occurred */
abort();
}
}
else if (lhs < 0 && rhs < 0) {
if (lhs <= INT_MIN - rhs) {
/* overflow has occurred */
abort();
}
}
return lhs + rhs;
}
Dies scheint vielversprechender zu sein, da wir die beiden Ganzzahlen erst dann addieren, wenn wir im Voraus sicherstellen, dass das Durchführen einer solchen Addition nicht zu einem Überlauf führt. Daher verursachen wir kein undefiniertes Verhalten.
Diese Lösung ist jedoch leider viel weniger effizient als die ursprüngliche Lösung, da Sie eine Subtraktionsoperation ausführen müssen, um zu testen, ob Ihre Additionsoperation funktioniert. Und selbst wenn Sie sich nicht für diesen (kleinen) Leistungseinbruch interessieren, bin ich immer noch nicht ganz davon überzeugt, dass diese Lösung angemessen ist. Der Ausdruck lhs <= INT_MIN - rhs
scheint genau der Art von Ausdruck zu sein, die der Compiler möglicherweise optimiert, da ein signierter Überlauf unmöglich ist.
Gibt es hier also eine bessere Lösung? Etwas, das garantiert 1) kein undefiniertes Verhalten verursacht und 2) dem Compiler keine Möglichkeit bietet, Überlaufprüfungen zu optimieren? Ich dachte, es könnte eine Möglichkeit geben, dies zu tun, indem beide Operanden in vorzeichenlose Zeichen umgewandelt werden und Überprüfungen durchgeführt werden, indem Ihre eigene Zwei-Komplement-Arithmetik gewürfelt wird, aber ich bin mir nicht sicher, wie ich das tun soll.