Pytanie dziwne zachowanie liczb całkowitych z gcc -O2


#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;
}

Kiedy skompiluję powyższy program z gcc wtf.c, Otrzymuję oczekiwany wynik:

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

Jednak, gdy skompiluję program z gcc -O2 wtf.c, Otrzymuję inne wyniki:

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

Zwróć uwagę na ostatnie dwa wiersze. Co tu się dzieje? Czy gcc 4.6.3 optymalizuje nieco zbyt chętnie?

(Testowałem to również za pomocą g ++ 4.6.3 i zaobserwowałem to samo dziwne zachowanie, stąd tag C ++).


9
2017-10-04 14:06


pochodzenie


nie czuć się swobodnie udzielać porad dla potencjalnie bardziej doświadczonego programisty, ale w każdym razie może być przydatny dla niezbyt doświadczonych. Jeśli widzę "dziwne" różnice spowodowane tylko przez poziom optymalizacji, pierwszą rzeczą, którą będę szukać, to UB. - ThomasMore


Odpowiedzi:


Kiedy to robisz - (INT_MIN), wywołujesz niezdefiniowane zachowanie, ponieważ ten wynik nie mieści się w int.

gcc -O2 zauważa, że ​​x nigdy nie może być ujemny i później się optymalizuje. Nie przejmuje się tym, że przepełniłeś wartość, ponieważ jest ona niezdefiniowana i może traktować ją tak, jak chce.


15
2017-10-04 14:11



Nic nie stoi na przeszkodzie, aby kompilator zdefiniował to, co jest standardem UB w standardzie, takie jak arytmetyczna dopełnienie dwóch elementów uzupełniających. Tak więc trzeba się zastanawiać, co to jest zasiłek robienia rzekomej optymalizacji? Brak, o ile widzę. To idiotyczna optymalizacja, IMHO. Tak więc, podczas gdy kompilator ma formalne prawa, jest to niska jakość wykonania przynajmniej pod tym względem. - Cheers and hth. - Alf
Oto przykład odpowiedzi pedr0. Aby uzyskać więcej informacji zobacz tutaj: blog.llvm.org/2011/05/... - Per Johansson
bardzo dziękuję za link! zauważ, że dwa pierwsze akapity silnie sugerują logiczne błędy typu "X to jeden z możliwych sposobów robienia Y, dlatego X jest wymagane dla Y". przestałem czytać po tym ... :-) - Cheers and hth. - Alf
@ Cheersandhth.-Alf Czy możesz określić, jakie sformułowania sugerują taki błąd? Nie znalazłem żadnego w pierwszych dwóch akapitach części "oedflow" lub całego artykułu. - Daniel Fischer
@ Danielan: na przykład, w pierwszym akapicie, << wiedząc, że INT_MAX + 1 jest niezdefiniowany pozwala na optymalizację "X + 1> X" na "prawda" >> jako przykład << włącza pewne klasy optymalizacji >> implikuje [ ponieważ X = przepełnienie-jest-UB jest jednym ze sposobów osiągnięcia Y = optymalizacja-z-x + 1> x, X = przepełnienie-jest-UB jest wymagane dla Y = optymalizacja-z-x + 1> x]. Co jest błędem. W drugim akapicie umyślna ignorancja logiki, lub oszustwo, jeśli chcesz, jest jeszcze gorsza: << [omija], a następnie wyłącza te ważne optymalizacje pętli >> jest rażąco nieprawdziwa jako ogólne stwierdzenie (które udaje). - Cheers and hth. - Alf


Myślę, że to może ci pomóc, jest stąd:tutaj

-fstrict-overflow   Pozwól kompilatorowi na przyjęcie ściśle podpisanych reguł przepełnienia, w zależności od kompilowanego języka. W przypadku C (i C ++) oznacza to, że przepełnienie podczas wykonywania arytmetyki ze znakowanymi liczbami jest niezdefiniowane, co oznacza, że ​​kompilator może założyć, że tak się nie stanie. Pozwala to na różne optymalizacje. Na przykład, kompilator przyjmie, że wyrażenie takie jak i + 10> i będzie zawsze prawdziwe dla podpisanego i. To założenie jest poprawne tylko wtedy, gdy podpisane przepełnienie jest niezdefiniowane, ponieważ wyrażenie jest fałszywe, jeśli przepełnienie i + 10 jest używane w arytmetyce dwójek uzupełnień. Gdy ta opcja jest włączona, każda próba ustalenia, czy operacja na liczbach podpisanych zostanie przepełniona, musi być napisana starannie, aby nie spowodować przepełnienia.   Ta opcja pozwala również kompilatorowi na przyjęcie surowej semantyki wskaźnika: dany wskaźnik do obiektu, jeśli dodanie przesunięcia do tego wskaźnika nie spowoduje utworzenia wskaźnika do tego samego obiektu, to dodanie jest niezdefiniowane. Pozwala to kompilatorowi wywnioskować, że p + u> p jest zawsze prawdziwe dla wskaźnika p i unsigned integer u. To założenie jest poprawne tylko dlatego, że zawijanie kursora jest niezdefiniowane, ponieważ wyrażenie jest fałszywe, jeśli p + u przepełni się, używając arytmetycznych dwóch elementów dopełnienia.

Zobacz także opcję -fwrapv. Użycie opcji -fwrapv oznacza, że ​​całkowite podpisane przepełnienie jest w pełni zdefiniowane: jest opakowane. Gdy użyto opcji -fwrapv, nie ma różnicy między parametrami -fstrict-overflow i -fno-strict-overflow dla liczb całkowitych. Dopuszczalne są niektóre typy przepełnienia -fwrapv. Na przykład, jeśli kompilator dostaje przepełnienie podczas wykonywania arytmetyki na stałych, przepełniona wartość może nadal być używana z opcją -fwrapv, ale nie w inny sposób.

Opcja -fstrict-overflow jest włączona na poziomach -O2, -O3, -Os.


12
2017-10-04 14:11