2012-10-04 13 views
9
#include <stdio.h> 
#include <limits.h> 

void sanity_check(int x) 
{ 
    if (x < 0) 
    { 
     x = -x; 
    } 
    if (x == INT_MIN) 
    { 
     printf("%d == %d\n", x, INT_MIN); 
    } 
    else 
    { 
     printf("%d != %d\n", x, INT_MIN); 
    } 
    if (x < 0) 
    { 
     printf("negative number: %d\n", x); 
    } 
    else 
    { 
     printf("positive number: %d\n", x); 
    } 
} 

int main(void) 
{ 
    sanity_check(42); 
    sanity_check(-97); 
    sanity_check(INT_MIN); 
    return 0; 
} 

Wenn ich das obige Programm mit gcc wtf.c kompilieren, erhalte ich die erwartete Ausgabe:seltsame integer Verhalten mit gcc -O2

42 != -2147483648 
positive number: 42 
97 != -2147483648 
positive number: 97 
-2147483648 == -2147483648 
negative number: -2147483648 

Allerdings, wenn ich das Programm mit gcc -O2 wtf.c kompilieren, erhalte ich einen anderen Ausgang :

42 != -2147483648 
positive number: 42 
97 != -2147483648 
positive number: 97 
-2147483648 != -2147483648 
positive number: -2147483648 

Beachten Sie die letzten beiden Zeilen. Was um alles in der Welt geht hier vor? Verbessert GCC 4.6.3 ein bisschen zu eifrig?

(I getestet dies auch mit g ++ 4.6.3, und ich beobachtete die gleiche seltsame Verhalten, also die C++-Tag.)

+0

nicht wohl fühlen, um Rat für möglicherweise viel erfahrenere Entwickler zu geben, aber auf jeden Fall könnte es nützlich sein für nicht so erfahren. Wenn ich "seltsame" Unterschiede sehe, die nur durch das Optimierungslevel verursacht werden, werde ich zuerst nach UB suchen. – ThomasMore

Antwort

15

Wenn Sie - (INT_MIN) ausführen, rufen Sie ein undefiniertes Verhalten auf, da dieses Ergebnis nicht in ein int passen kann.

gcc -O2 merkt, dass x niemals negativ sein kann und optimiert danach. Es ist nicht wichtig, dass Sie den Wert überfließen, da das nicht definiert ist und es kann behandelt werden, wie es will.

+0

Nichts hindert einen Compiler daran, das zu definieren, was UB im Standard ist, z. B. Nicht-Trapping-Zweier-Komplement-Arithmetik. Man muss sich fragen, was ist der Vorteil der angeblichen Optimierung? Soweit ich sehen kann. Es ist eine Optimierung des Idioten, IMHO. Während also der Compiler formal in seinen Rechten ist, ist es zumindest in dieser Hinsicht eine ** minderwertige Implementierung **. –

+2

Es gibt ein Beispiel in pedr0's Antwort. Für einige mehr sehen Sie hier: http: //blog.llvm.org/2011/05/was-jeder-c-programmierer-sollte-wissen.html # signed_overflow –

+0

vielen dank für den link! Beachten Sie, dass die ersten beiden Absätze stark logische Irrtümer der Art "X ist ein möglicher Weg, um Y zu tun, also X ist für Y erforderlich" bedeuten. Ich habe danach aufgehört zu lesen ... :-) –

12

ich denke, das könnte Ihnen helfen, von hier aus ist: here

-fstrict-overflow Erlauben Sie dem Compiler abhängig von der Sprache, die kompiliert wird, strikte überzeichnete Überlaufregeln. Für C (und C++) bedeutet dies, dass der Überlauf bei der Arithmetik mit vorzeichenbehafteten Zahlen undefiniert ist, was bedeutet, dass der Compiler davon ausgehen kann, dass dies nicht passieren wird. Dies ermöglicht verschiedene Optimierungen. Zum Beispiel nimmt der Compiler an, dass ein Ausdruck wie i + 10> i für signed i immer wahr ist. Diese Annahme ist nur gültig, wenn der Überlauf mit Vorzeichen undefiniert ist, da der Ausdruck falsch ist, wenn i + 10 bei Verwendung von Zweierkomplementarithmetik überläuft. Wenn diese Option aktiviert ist, muss jeder Versuch, festzustellen, ob eine Operation für vorzeichenbehaftete Zahlen überläuft, sorgfältig geschrieben werden, um einen Überlauf zu vermeiden. Mit dieser Option kann der Compiler auch eine strikte Pointer-Semantik annehmen: Wenn ein Pointer zu einem Objekt hinzugefügt wird und ein Offset zu diesem Pointer keinen Zeiger auf dasselbe Objekt erzeugt, ist die Addition nicht definiert. Dies erlaubt dem Compiler, zu folgern, dass p + u> p immer wahr ist für einen Zeiger p und eine vorzeichenlose ganze Zahl u. Diese Annahme ist nur gültig, weil der Zeigerumbruch nicht definiert ist, da der Ausdruck falsch ist, wenn p + u mit Zweierkomplementarithmetik überläuft.

Siehe auch die Option -fwrapv. Die Verwendung von -fwrapv bedeutet, dass der integer signed overflow vollständig definiert ist: es wird umbrochen. Wenn -fwrapv verwendet wird, gibt es keinen Unterschied zwischen -fstrict-overflow und -fno-strict-overflow für Ganzzahlen. Mit -fwrapv sind bestimmte Arten von Überläufen erlaubt. Wenn der Compiler beispielsweise einen Überlauf erhält, wenn er arithmetisch für Konstanten arbeitet, kann der übergelaufene Wert immer noch mit -fwrapv verwendet werden, ansonsten jedoch nicht.

Die Option -fstrict-overflow ist in den Ebenen -O2, -O3, -Os aktiviert.