Hier ist eine C-Funktion, die eine int
zu einer anderen hinzufügt und fehlschlägt, wenn ein Überlauf auftreten würde:
int safe_add(int *value, int delta) {
if (*value >= 0) {
if (delta > INT_MAX - *value) {
return -1;
}
} else {
if (delta < INT_MIN - *value) {
return -1;
}
}
*value += delta;
return 0;
}
Leider ist es von GCC oder Clang nicht gut optimiert :
safe_add(int*, int):
movl (%rdi), %eax
testl %eax, %eax
js .L2
movl $2147483647, %edx
subl %eax, %edx
cmpl %esi, %edx
jl .L6
.L4:
addl %esi, %eax
movl %eax, (%rdi)
xorl %eax, %eax
ret
.L2:
movl $-2147483648, %edx
subl %eax, %edx
cmpl %esi, %edx
jle .L4
.L6:
movl $-1, %eax
ret
Diese Version mit __builtin_add_overflow()
int safe_add(int *value, int delta) {
int result;
if (__builtin_add_overflow(*value, delta, &result)) {
return -1;
} else {
*value = result;
return 0;
}
}
ist besser optimiert :
safe_add(int*, int):
xorl %eax, %eax
addl (%rdi), %esi
seto %al
jo .L5
movl %esi, (%rdi)
ret
.L5:
movl $-1, %eax
ret
Aber ich bin gespannt, ob es einen Weg gibt, ohne eingebaute Elemente zu verwenden, die von GCC oder Clang musterangepasst werden.
c
gcc
optimization
clang
integer-overflow
Tavian Barnes
quelle
quelle
Antworten:
Das Beste, was ich mir ausgedacht habe, ist, Dinge zu erledigen, wenn Sie keinen Zugriff auf die Überlaufflagge der Architektur haben
unsigned
. Denken Sie hier nur an alle Bitarithmetik, da wir nur an dem höchsten Bit interessiert sind, das das Vorzeichenbit ist, wenn es als vorzeichenbehaftete Werte interpretiert wird.(All diese Modulo-Vorzeichenfehler habe ich nicht gründlich überprüft, aber ich hoffe, die Idee ist klar)
Wenn Sie eine Version des Zusatzes finden, die frei von UB ist, z. B. eine atomare, ist der Assembler sogar ohne Verzweigung (jedoch mit einem Sperrpräfix).
Wenn wir also eine solche Operation hätten, aber noch "entspannter", könnte dies die Situation noch weiter verbessern.
Take3: Wenn wir eine spezielle "Besetzung" vom nicht signierten zum signierten Ergebnis verwenden, ist diese jetzt verzweigungsfrei:
quelle
unsigned
. Dies hängt jedoch davon ab, dass beim vorzeichenlosen Typ nicht nur das Vorzeichenbit ausgeblendet ist. (Beide sind jetzt in C2x garantiert, dh für alle Bögen, die wir finden konnten). Dann können Sie dasunsigned
Ergebnis nicht zurückgeben, wenn es größer alsINT_MAX
ist. Dies wäre eine definierte Implementierung und kann ein Signal auslösen.Die Situation bei signierten Operationen ist viel schlimmer als bei nicht signierten, und ich sehe nur ein Muster für die signierte Addition, nur für das Klirren und nur, wenn ein breiterer Typ verfügbar ist:
clang gibt genau das gleiche asm wie bei __builtin_add_overflow:
Ansonsten ist die einfachste Lösung, die ich mir vorstellen kann, folgende (mit der Schnittstelle als Jens):
gcc und clang erzeugen einen sehr ähnlichen asm . gcc gibt dies:
Wir wollen die Summe in berechnen
unsigned
,unsigned
müssen also in der Lage sein, alle Werte von darzustellen,int
ohne dass einer von ihnen zusammenklebt. Um das Ergebnis einfach vonunsigned
nach zu konvertierenint
, ist auch das Gegenteil sinnvoll. Insgesamt wird das Zweierkomplement angenommen.Ich denke, wir können auf allen gängigen Plattformen konvertieren
unsigned
int
durch eine einfache Zuordnung zu ,int sum = u;
aber wie Jens erwähnte, ermöglicht es sogar die neueste Variante des C2x-Standards, ein Signal zu erzeugen. Der nächst natürlichste Weg ist, so etwas zu tun:*(unsigned *)&sum = u;
Aber Nicht-Trap-Varianten der Polsterung können sich offenbar für signierte und nicht signierte Typen unterscheiden. Das obige Beispiel geht also den harten Weg. Glücklicherweise optimieren sowohl gcc als auch clang diese knifflige Konvertierung.PS Die beiden oben genannten Varianten konnten nicht direkt verglichen werden, da sie sich unterschiedlich verhalten. Die erste folgt der ursprünglichen Frage und blockiert die
*value
bei Überlauf nicht. Der zweite folgt der Antwort von Jens und blockiert immer die Variable, auf die der erste Parameter zeigt, aber sie ist verzweigungslos.quelle
Die beste Version, die ich finden kann, ist:
welches produziert:
quelle
int
ein Cast von einem breiteren Typ außerhalb des Bereichs von liegt , wird entweder ein implementierungsdefinierter Wert erzeugt oder ein Signal ausgelöst. Alle Implementierungen, die mir wichtig sind, definieren es, um das Bitmuster beizubehalten, das das Richtige tut.Ich könnte den Compiler dazu bringen, das Vorzeichen-Flag zu verwenden, indem ich eine Zweierkomplementdarstellung annehme (und bestätige), ohne Bytes aufzufüllen. Solche Implementierungen sollten das erforderliche Verhalten in der durch einen Kommentar kommentierten Zeile ergeben, obwohl ich im Standard keine positive formale Bestätigung dieser Anforderung finden kann (und es wahrscheinlich keine gibt).
Beachten Sie, dass der folgende Code nur die positive Ganzzahladdition behandelt, aber erweitert werden kann.
Dies ergibt sowohl Clang als auch GCC:
quelle
_Static_assert
dient nicht viel einem Zweck, da dies für jede aktuelle Architektur trivial gilt und sogar für C2x auferlegt wird.INT_MAX
. Ich werde den Beitrag bearbeiten. Andererseits denke ich nicht, dass dieser Code in der Praxis sowieso verwendet werden sollte.