2012-10-04 3 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; 
} 

Quando compilo il programma di cui sopra con gcc wtf.c, ottengo i risultati attesi:strano comportamento intero con gcc -O2

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

Tuttavia, quando compilo il programma con gcc -O2 wtf.c, ottengo un output diverso :

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

Nota le ultime due righe. Cosa diavolo sta succedendo qui? Gcc 4.6.3 sta ottimizzando troppo un po 'troppo?

(ho anche provato questo con g ++ 4.6.3, e ho osservato lo stesso comportamento strano, quindi tag C++.)

+0

non si sentono a proprio agio a dare consigli per possibilmente sviluppatori molto più esperti, ma in ogni caso potrebbe essere utile per non così esperti. Se vedo differenze "strane" causate solo dal livello di ottimizzazione, la prima cosa che cercherò è UB. – ThomasMore

risposta

15

Quando si esegue l'operazione - (INT_MIN) si sta invocando un comportamento non definito, poiché tale risultato non può rientrare in un int.

gcc -O2 nota che x non può mai essere negativo e ottimizza in seguito. Non gli importa che tu abbia superato il valore dal momento che è indefinito e può trattarlo come vuole.

+0

Niente impedisce al compilatore di definire ciò che è UB nello standard, come ad esempio l'aritmetica del complemento a due di non intrappolamento. Quindi ci si deve chiedere, qual è il * vantaggio * di fare la presunta ottimizzazione? Nessuno, per quanto posso vedere. È un'ottimizzazione idiota, IMHO. Quindi, mentre il compilatore è formalmente nei suoi diritti, è una ** implementazione di bassa qualità ** almeno in questo senso. –

+2

C'è un esempio nella risposta di pedr0. Per altri ancora vedere qui: http: //blog.llvm.org/2011/05/what-every-c-programmer-should-know.html # signed_overflow –

+0

grazie per il link! si noti che i primi due paragrafi implicano fortemente fallacie logiche del tipo "X è un possibile modo di fare Y, quindi X è richiesto per Y". Ho smesso di leggere dopo ... :-) –

12

Credo che questo potrebbe aiutare, è da qui: here

-ftrict-overflow Consentire al compilatore di assumere regole di overflow con segno rigoroso, in base alla lingua da compilare. Per C (e C++) ciò significa che l'overflow quando si esegue l'aritmetica con i numeri firmati non è definito, il che significa che il compilatore può presumere che non accadrà. Ciò consente varie ottimizzazioni. Ad esempio, il compilatore assumerà che un'espressione come i + 10> i sarà sempre vera per l'i firmato. Questa ipotesi è valida solo se l'overflow con segno è indefinito, poiché l'espressione è falsa se i + 10 overflow quando si utilizza l'aritmetica a complemento di due. Quando questa opzione è effettiva, qualsiasi tentativo di determinare se un'operazione su numeri firmati avrà un overflow deve essere scritto attentamente per non coinvolgere effettivamente l'overflow. Questa opzione consente inoltre al compilatore di assumere una semantica puntuale del puntatore: dato un puntatore a un oggetto, se l'aggiunta di un offset a quel puntatore non produce un puntatore allo stesso oggetto, l'aggiunta non è definita. Ciò consente al compilatore di concludere che p + u> p è sempre vero per un puntatore p e un intero senza segno u. Questa ipotesi è valida solo perché il wrapper del puntatore non è definito, in quanto l'espressione è falsa se p + u è in overflow utilizzando l'aritmetica dei due complementari.

Vedere anche l'opzione -fwrapv. L'uso di -fwrapv significa che l'overflow con segno intero è completamente definito: si avvolge. Quando viene utilizzato -fwrapv, non vi è alcuna differenza tra -fstrict-overflow e -fno-strict-overflow per i numeri interi. Con -fwrapv sono consentiti determinati tipi di overflow. Ad esempio, se il compilatore ottiene un overflow durante l'aritmetica sulle costanti, il valore di overflow può ancora essere utilizzato con -fwrapv, ma non altrimenti.

L'opzione -fintict-overflow è abilitata a livelli -O2, -O3, -Os.