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.
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
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ihren Ansatz mit der Subtraktion ist richtig und gut definiert. Ein compiler nicht optimieren Sie Weg.
Anderen die richtige Vorgehensweise, wenn Sie eine größere integer-Typ zur Verfügung, um die arithmetische Operationen ausführen, die in den größeren Typ und überprüfen Sie, dass das Ergebnis passt in der kleineren Art, wenn Sie zu konvertieren zurück
Einen guten compiler konvertiert die gesamten neben-und
if
Anweisung in einenint
-size-Zusatz und ein einziger bedingter Sprung-auf-überlauf und eigentlich nie führen Sie die größere neben.Edit: Als Stephen darauf hingewiesen, ich habe Mühe, eine (nicht so gute) compiler gcc, die zum erzeugen der sane asm. Den code den es generiert, ist das nicht furchtbar langsam, aber sicher suboptimal. Wenn jemand weiß, Varianten dieses Codes, die Sie erhalten, gcc, das richtige zu tun, ich würde lieben, Sie zu sehen.
long long
vor der Zugabe.Aus Neugier, haben Sie erfolgreich waren, bekommen Sie einen compiler diese Optimierung? Ein kurzer test vor ein paar Compiler wiederum nicht jede, die es tun könnten.
Auf x86_64, es gibt nichts, ineffizient, über die Verwendung von 32-bit-Ganzzahlen. Leistung ist identisch mit der 64-bit Variante. Eine motivation für die Verwendung von kleiner-als-native-word-size-Typen ist, dass es äußerst effizient zu handhaben überlauf Bedingungen oder tragen (für eine beliebige Präzision der Arithmetik), da der überlauf/carry geschieht in einem direkt zugänglichen Ort.
Nein, die Subtraktion-code, den der OP gab ist nicht richtig, siehe meine Antwort. Ich gebe auch einen code gibt, macht es einfach mit zwei Vergleiche. Vielleicht ist der Compiler besser machen wird.
Dieser Ansatz funktioniert nicht, in der ungewöhnliche Plattform wo
sizeof(long long) == sizeof(int)
. C gibt nur an, dasssizeof(long long) >= sizeof(int)
.InformationsquelleAutor R..
Nein, dein 2. code ist nicht korrekt, aber Sie sind in der Nähe: wenn Sie
das Ergebnis einer addition ist
INT_MAX
. (INT_MAX
ist immer eine ungerade Zahl). Das ist also eine gültige Eingabe. Aber in Ihrer routine haben SieINT_MAX - half == half1
und Sie würde Abbrechen. Ein false positive ist.Dieser Fehler kann repariert werden, indem
<
statt<=
in beiden Prüfungen.Aber dann auch dein code ist nicht optimal. Die folgenden tun würde:
Zu sehen, dass diese gültig ist, müssen Sie symbolisch hinzufügen
lhs
auf beiden Seiten der Ungleichungen, und das gibt Ihnen genau die arithmetischen Bedingungen, die Ihr Ergebnis ist außerhalb des gültigen Bereichs./* overflow will occurred */
zu betonen, dass der springende Punkt ist, zu erkennen, wenn überlauf aufgetreten wären, wenn der code hatlhs + rhs
ohne dabei wirklich die Summe.InformationsquelleAutor Jens Gustedt
IMHO, die eastiest Umgang mit überlauf sentsitive C++ - code ist die Verwendung
SafeInt<T>
. Dies ist ein cross-Plattform C++ - template-gehostet-code plex, die für die Sicherheit garantiert, dass Sie den Wunsch hier.Ich finde es sehr intuitiv zu bedienen, wie es bietet viele der gleichen Nutzungsmuster wie normale numerische opertations und drückt über und unter fließt über Ausnahmen.
InformationsquelleAutor JaredPar
Für die gcc Fall von gcc 5.0 Release notes können wir sehen, es bietet jetzt eine
__builtin_add_overflow
für die überprüfung von überlauf zusätzlich:Beispiel:
Können wir sehen, aus den gcc-Dokument Built-in-Funktionen zum Durchführen von Arithmetischen mit Überlauf Prüfen:
clang bietet auch eine Reihe von geprüft arithmetische gelieferten:
in diesem Fall die builtin wäre:
int result; __builtin_add_overflow(INT_MAX, 1, &result);
nicht explizit sagen, was gespeichert wird, inresult
auf überlauf und leider ist ruhig auf die Angabe von Undefiniertes Verhalten ist nicht auftreten. Sicherlich, dass war der Vorsatz - keine UB. Besser, wenn es angegeben ist.guter Punkt, es heißt hier das Ergebnis ist immer definiert, ich aktualisierte meine Antwort. Es wäre ziemlich ironisch, wenn das nicht der Fall war.
Interessant ist die neue Referenz für keinen
(unsigned) long long *result
für__builtin_(s/u)addll_overflow
. Sicherlich sind diese Fehler. Macht ein Wunder, als um die Wahrhaftigkeit des anderen Aspekten. IAC, gut zu sehen, diese__builtin_add/sub/mull_overflow()
. Hoffe, dass Sie es bis in die C-spec irgendwann.+1 dies erzeugt viel bessere Montage als alles, was Sie bekommen konnte in standard-C, zumindest nicht, ohne sich auf Ihre compiler-Optimierer, um herauszufinden, was Sie tun. Man sollte erkennen, wenn eine solche gelieferten verfügbar sind und verwenden Sie nur eine standard-Lösung, wenn der compiler nicht.
InformationsquelleAutor Shafik Yaghmour
Wenn Sie inline-assembler können Sie die overflow-flag. Eine andere Möglichkeit ist, welches Sie verwenden können, eine safeint Datentyp. Ich empfehle, Lesen Sie dieses Papier auf Integer-Sicherheit.
Ich gab ein downvote für ein asm-Antwort auf eine C-Frage. Wie ich schon sagte, es gibt richtige, tragbare Möglichkeiten, um das Kontrollkästchen schreiben in C generiert, die genau die gleiche asm, dass Sie mit der hand Schreibe. Natürlich, wenn Sie diese verwenden, die Auswirkungen auf die Leistung werden die gleichen sein, und es wird viel weniger Einfluss als die C++ safeint Sachen, die Sie auch empfohlen.
Wenn Sie code schreiben, die nur auf einer Implementierung und Umsetzung garantiert, dass etwas funktioniert, und Sie brauchen eine gute integer-performance, natürlich können Sie die Umsetzung-konkrete tricks. Das ist nicht das, was der OP gefragt wurde, aber.
C unterscheidet sich von der Implementierung definiert Verhalten und Undefiniertes Verhalten aus guten Gründen, und selbst wenn etwas mit UB "Werke" in die aktuelle version Ihrer Umsetzung, das bedeutet nicht, es wird weiterhin in zukünftigen Versionen funktionieren wird. Betrachten gcc und unterzeichnet überlauf...
Da ich basiert meine -1 auf einen Anspruch, den wir bekommen konnten C-code zu erzeugen, der identisch asm, ich denke es ist nur fair zu widerrufen, wenn alle wichtigen Compiler erweisen junk in dieser Hinsicht..
InformationsquelleAutor rook
Schnellste Möglichkeit ist die Verwendung der GCC-builtin:
Auf x86 mit GCC kompiliert diese in:
verwendet der Prozessor integrierten überlauf-Erkennung.
Wenn Sie nicht OK mit der Verwendung von GCC gelieferten, der nächste Schnellste Weg ist, um bit-Operationen auf die Zeichen-bits. Unterzeichnet überlauf zusätzlich tritt auf, wenn:
Dem Vorzeichen-bit des
~(lhs ^ rhs)
ist auf dem iff die Operanden das gleiche Vorzeichen, und das Vorzeichen-bit derlhs ^ sum
ist am iff das Ergebnis hat ein anderes Vorzeichen als die Operanden. So kann man den Zusatz in unsignierter form zu vermeiden, zu undefiniertem Verhalten, und verwenden Sie dann das Vorzeichen-bit der~(lhs ^ rhs) & (lhs ^ sum)
:Diese kompiliert in:
das ist ziemlich viel schneller als die Umwandlung einer 64-bit-geben Sie auf einem 32-bit-Maschine (mit gcc):
InformationsquelleAutor tbodt
Wie etwa:
Ich denke, dass sollte funktionieren für legitim
INT_MIN
undINT_MAX
(symmetrisch oder nicht); die Funktion wie clips, aber es sollte offensichtlich sein, wie man andere Verhaltensweisen).Ich denke, dass diese -
result = (n1 - INT_MAX)+n2;
- könnte überlaufen, wenn n1 kleiner war (sagen wir mal 0) und n2 negativ war.Hmm... vielleicht ist es notwendig, zu brechen, aus drei Fällen: beginnen Sie mit einer für
(n1 ^ n2) < 0
, die auf einem zweier-Komplement-Maschine würde bedeuten, dass die Werte haben entgegengesetzte Vorzeichen und kann direkt aufgenommen werden. Wenn die Werte haben das gleiche Vorzeichen, dann ist der Ansatz oben gegeben wäre sicher. Auf der anderen Seite, ich bin gespannt, ob die Autoren der Norm zu erwarten, dass Implementierungen für zweier-Komplement-silent-overflow-hardware würde den Sprung über die Schienen im Falle von überlauf in einer Weise, die nicht die Kraft eine unmittelbare Abnormale Beendigung des Programms, sondern verursacht unvorhersehbare Störungen von anderen Berechnungen.InformationsquelleAutor supercat
Haben Sie vielleicht mehr Glück umwandeln zu 64-bit-Ganzzahlen und Tests zu ähnlichen Bedingungen wie die. Zum Beispiel:
Möchten Sie vielleicht, um einen genaueren Blick auf, wie Zeichen-Erweiterung, die hier arbeiten, aber ich denke, es ist richtig.
Sie sind richtig, dass ich einfach so explizit über meine casts. Ich werde es ändern für die Richtigkeit, obwohl. Für zukünftige Leser, die return-Zeile Lesen
return (int32_t)(sum & 0xffffffff);
.Beachten Sie, dass wenn Sie schreiben
sum & 0xffffffff
,sum
implizit in Typunsigned int
(vorausgesetzt, die 32-bit -int
), weil0xffffffff
Typunsigned int
. Dann wird das Ergebnis des bitweisen und ist einunsigned int
, und wennsum
negativ war, wird es sein, die außerhalb des Bereichs von Werten, unterstützt durchint32_t
. Die Umstellung aufint32_t
dann hat implementierungsspezifische Verhalten.InformationsquelleAutor Jonathan
Mir das einfachste zu überprüfen wäre, überprüfen Sie die Zeichen der Operanden und die Ergebnisse.
Betrachten wir die Summe: der überlauf, der auftreten konnte, in beide Richtungen, + oder -, nur dann, wenn beide Operanden das gleiche Vorzeichen. Und, obviosly, der überlauf sein wird, wenn das Vorzeichen des Ergebnisses ist nicht das gleiche wie das Vorzeichen des Operanden.
So, schauen wie das wird genug sein:
Edit: wie Nils vorgeschlagen, das ist das richtige
if
Zustand:Und seit Wann wird der Unterricht
führt zu undefiniertem Verhalten? Es gibt keine solche Sache in der Intel x86-Befehlssatz refference..
sum = a + b
könnten Ertrag zu undefiniertem Verhalten.wenn Sie die Umwandlung Summe a und b unsigned während Ihres test-neben Ihren code funktioniert btw..
Es ist undefiniert, nicht weil das Programm abstürzt oder sich anders verhält. Es ist die genaue Sache, der Prozessor ist zu tun, um zu berechnen, Flagge. Der standard ist nur ein Versuch, um sich zu schützen, die von nicht-standard-Fällen, aber es bedeutet nicht, dass Sie nicht erlaubt, dies zu tun.
ja, ich wollte das tun, aber ich dachte, dass vier
(usngined int)
s wird es viel mehr unlesbar. (Sie wissen, Sie zuerst Lesen es, und versuchen Sie es nur, wenn es Euch gefallen hat).das Undefinierte Verhalten ist in C, die nicht nach dem kompilieren der Montage
InformationsquelleAutor ruslik
Ist die offensichtliche Lösung ist die Konvertierung zu unsigned, um die gut definierten unsigned overflow-Verhalten:
Dieser ersetzt den unbestimmten signed overflow-Verhalten mit der Implementierung definierte Umwandlung von out-of-range-Werte zwischen signed und unsigned, so müssen Sie überprüfen Ihre compiler-Dokumentation genau zu wissen, was passieren wird, aber es sollte zumindest definiert werden, und sollte das richtige tun, auf jede zweier-Komplement-Maschine, die nicht erhöhen Signale auf conversions, die so ziemlich jede Maschine und C-compiler gebaut, der in den letzten 20 Jahren.
sum
, die eineint
. Das ergibt entweder eine von der Implementierung definierte Ergebnis oder eine von der Implementierung definierte signal wird ausgelöst, wenn der Wert der(unsigned)lhs + (unsigned)rhs
größer ist alsINT_MAX
.das ist der springende Punkt-das Verhalten wird durch die Implementierung definiert, sondern als undefiniert, so dass die Umsetzung auch zu dokumentieren, was es tut, und tun es konsequent. Ein signal kann nur erhöht werden, wenn die Umsetzung dokumentiert, in welchem Fall es muss immer erhoben werden und die Sie verwenden können, Verhalten.
InformationsquelleAutor Chris Dodd
Im Falle der Zugabe von zwei
long
Werte, portablen code aufteilen können, dielong
- Wert in low und highint
Teile (oder inshort
Teile im Fallelong
hat die gleiche Größe wieint
):Verwendung von inline-assembly ist der Schnellste Weg, wenn ein bestimmtes targeting CPU:
InformationsquelleAutor atomsymbol
Ich denke, dass das funktioniert:
Verwendung von volatile hält der compiler die Optimierung Weg, der test, weil es denkt, dass
sum
geändert haben kann, zwischen der addition und der Subtraktion.Mit gcc 4.4.3 für x86_64 die Montage dieser code macht die addition, die Subtraktion und den test, aber es speichert alles auf dem stack und nicht mehr benötigte stack-Operationen. Ich habe sogar versucht
register volatile int sum =
aber die Montage war die gleiche.Für eine version mit nur
int sum =
(keine flüchtigen oder register) die Funktion nicht testen und habe den Zusatz mit nur einemlea
Unterricht (lea
Laden Effektive Adresse und wird oft verwendet, um zu tun, zudem ohne Berührung des flags-Registers).Ihre version ist größer-code und hat viel mehr Sprünge, aber ich weiß nicht, was wäre besser.
volatile
Maske zu undefiniertem Verhalten. Wenn es "funktioniert", Sie sind immer noch nur "immer Glück".Wenn es nicht funktioniert der compiler nicht die Umsetzung
volatile
richtig. Alles was ich versuchte, war eine einfachere Lösung für ein sehr häufiges problem bei einer bereits beantworteten Frage.Wo könnte es scheitern, obwohl, wäre ein system, dessen numerische Darstellung wickeln zu niedrigeren Werten bei Pufferüberlauf für Ganzzahlen.
Das Letzte Kommentar sollte ein "nicht" oder "gar nicht".
Ihre Behauptung, dass "wenn es nicht funktioniert der compiler nicht die Umsetzung volatile-korrekt" ist falsch. Für eine Sache, die in Zweierkomplement-Arithmetik, es wird immer wahr sein, dass lhs = Summe - rhs, auch wenn überlauf aufgetreten. Selbst wenn das nicht der Fall, und zwar in diesem bestimmten Beispiel ist ein bisschen konstruiert, der compiler könnte beispielsweise code generieren führt, die zudem speichert das Ergebnis Wert ist, liest den Wert wieder in ein anderes register, vergleicht den gespeicherten Wert mit dem Wert Lesen und bemerkt, dass Sie identisch sind und übernehmen daher auch keine überlauf aufgetreten ist.
InformationsquelleAutor nategoose