Erkennen signed-überlauf in C/C++

Auf den ersten Blick, diese Frage mag wie eine Kopie der Wie zu erkennen integer-überlauf?, aber es ist tatsächlich deutlich anders.

Habe ich gefunden, dass während der Erkennung eine vorzeichenlose integer-überlauf ist ziemlich trivial, das erkennen einer unterzeichnet überlauf in C/C++ ist eigentlich schwieriger, als die meisten Leute denken.

Den meisten offensichtlich, doch naiv, so zu tun, es wäre so etwas wie:

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 mit diesem ist, dass nach dem C-standard, Ganzzahl-überlauf ist Undefiniertes Verhalten. In anderen Worten, gemäß der Norm, sobald Sie selbst die Ursache unterzeichnet überlauf, Ihr Programm ist genauso hinfällig, wie wenn Sie dereferenziert einen null-Zeiger. Man kann also nicht die Ursache zu undefiniertem Verhalten, und versuchen Sie dann zu erkennen, den überlauf nach der Tat, wie in der oben genannten post-condition check Beispiel.

Obwohl die obige Prüfung ist wahrscheinlich zu arbeiten auf viele Compiler können Sie nicht rechnen. In der Tat, da der C-standard sagt signed integer overflow nicht definiert ist, sind einige Compiler (z.B. GCC) wird optimieren Sie Weg, die obige Prüfung wenn Optimierungs-flags gesetzt sind, da der compiler davon eine signiert überlauf ist unmöglich. Diese völlig unterbricht den Versuch, zu prüfen, für überlauf.

So, eine andere Möglichkeit zu prüfen, für überlauf 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;
}

Dieser scheint vielversprechend, da wir eigentlich nicht hinzuzufügen, die zwei Ganzzahlen miteinander, bis wir stellen Sie im Voraus sicher, dass die Durchführung solch einer Beurteilung führt nicht zum überlaufen. Also, wir führen nicht irgendwelche undefinierten Verhalten.

Jedoch, diese Lösung ist leider sehr viel weniger effizient als die erste Lösung, da Sie zum durchführen einer subtract-operation, nur um zu testen, ob die addition funktioniert. Und selbst wenn Sie don ' T care über diese (kleinen) Leistungseinbruch, ich bin noch nicht ganz überzeugt, dass diese Lösung ausreichend ist. Der Ausdruck lhs <= INT_MIN - rhs scheint, genau wie die Art von Ausdruck, der compiler kann optimieren entfernt werden, zu denken, dass signed-überlauf ist unmöglich.

So gibt es eine bessere Lösung? Etwas, das garantiert wird, um 1) führen nicht zu undefiniertem Verhalten, und 2) nicht die compiler die Möglichkeit zur Optimierung entfernt überlauf überprüft? Ich dachte vielleicht gibt es einen Weg, es zu tun, indem man beide Operanden in unsigned und die Durchführung von Kontrollen durch Ihre eigenen Rollen zweier-Komplement-Arithmetik, aber ich bin mir nicht wirklich sicher, wie das zu tun.

Dann lieber der Versuch zu erkennen, ist es nicht besser, Verfolgung, code zu schreiben, die nicht die Möglichkeit haben, den überlauf?
Es ist wirklich schwer zu nehmen Kalkulationen und gewährleisten, dass Sie nicht überlaufen, und es ist unmöglich zu beweisen, der Allgemeine Fall. Die übliche Praxis ist die Verwendung als breit ein integer-Typ, wie möglich und hoffen.
Die Dereferenzierung eines null-Zeiger ist ebenso undefiniert wie signed-überlauf. Undefiniertes Verhalten bedeutet, dass, so weit wie die Standard geht, kann alles passieren. Man kann nicht davon ausgehen, dass das system nicht in einen unwirksamen und instabilen Zustand nach Unterzeichnung überlauf. Der OP wies darauf hin, eine Folge davon: es ist völlig legal für den optimizer zu entfernen-code erkennt, unterzeichnet überlauf, sobald es passiert.
Habe ich erwähnt wie eine Implementierung. GCC wird entfernen die overflow-check-code, wenn Optimierungs-flags gesetzt sind. So wird es im Grunde brechen Sie Ihr Programm. Dies ist wohl schlimmer als einen null-Zeiger zu dereferenzieren, da es führen kann, subtile Sicherheitslücken in der Erwägung, dass die Dereferenzierung eines null-wahrscheinlich gerade unverblümt verpasste Ihr Programm mit einem segfault.
Ich habe sicherlich scheinen Implementierungen, in denen, je nach compiler-Einstellungen, überlauf verursachen würde, eine Falle. Es wäre schön, wenn Sprachen darf man angeben, ob auf bestimmte unsigned Variablen oder Mengen sollte (1) wickeln Sie sauber, (2) Fehler, oder (3) tun, was ist bequem. Beachten Sie, dass, wenn eine variable kleiner ist als eine Maschine, die register Größe, die verlangt, dass nicht signierte Mengen sauber wickeln kann verhindern, dass die Erzeugung optimaler code.

InformationsquelleAutor Channel72 | 2010-10-15

Schreibe einen Kommentar