#include <stdio.h>
#include <stdint.h>
int main()
{
int32_t a = 0;
for (int32_t p = 0x7fffffff; p > 0; p++) a += p;
puts("END");
return a;
}
mingw64 v.8.1.0
Zmienna p nie zmienia swojej wartości i pętla for nigdy się nie kończy. Dlaczego?
#include <stdio.h>
#include <stdint.h>
int main()
{
int32_t a = 0;
for (int32_t p = 0x7fffffff; p > 0; p++) a += p;
puts("END");
return a;
}
mingw64 v.8.1.0
Zmienna p nie zmienia swojej wartości i pętla for nigdy się nie kończy. Dlaczego?
p > 0
wykonaj petle dopóki p jest większe niż zero
zadeklarowana wartość początkowa jest wieksza niz zero
pozniej zwieksza to p
zapewne az wyjdzie jakiś overflow inta co jest UB więc dalej Ci nie powiem co sie dzieję z programem
A jak sprawdziłeś, że zmienna p
nie zmienia swojej wartości?
Kod powinien się zakończyć po pewnym czasie gdy nastąpi przepełnienie, tzn. p
się przekręci i osiągnie wartość -2147483648. Na słabszych maszynach może to potrwać kilka chwil.
rrowniak napisał(a):
A jak sprawdziłeś, że zmienna
p
nie zmienia swojej wartości?
Kod powinien się zakończyć po pewnym czasie gdy nastąpi przepełnienie, tzn.p
się przekręci i osiągnie wartość -2147483648. Na słabszych maszynach może to potrwać kilka chwil.
#include <stdio.h>
#include <stdint.h>
int main()
{
int32_t a = 0;
for (int32_t p = 0x7fffffff; p > 0; p++) {
printf("0x%x\n", p);
a += p;
}
puts("END");
return a;
}
I w tym problem, że się nie kończy :)
tak jak pisalem jest to UB i zapewne bedzie zaleznie od czynnikow jak:
ale nadal, jako ze to UB nie powinienes spodziewac sie jakiego kolwiek wyniku bo z tego kodu mogą wyjść nasal demons
Po inkrementacji powinna nastąpić zmiana znaku i zakończenie pętli. Tak się jednak nie dzieje.
Nieprawda - https://en.cppreference.com/w/cpp/language/ub: Examples of undefined behavior are (...) signed integer overflow
Patryk27 napisał(a):
Po inkrementacji powinna nastąpić zmiana znaku i zakończenie pętli. Tak się jednak nie dzieje.
Nieprawda - https://en.cppreference.com/w/cpp/language/ub:
Examples of undefined behavior are (...) signed integer overflow
Zgadza się, optymalizacja zrobiła swoje.
PS.
Takie przemądrzałe kompilatory odbierają część radości z programowania :)
Co prawda to straszliwie brzydki hack, ale jeśli Bracie koniecznie chcesz aby to działało jak planujesz to zapisz pętlę tak:
for (volatile int32_t p = 0x7fffffff; p > 0; p++)
{
a += p;
}
niemniej wciąż powyższe to UB, i chapnie cię znienacka za zadziec w najmniej oczekiwanym momencie.
Jeśli ja miałbym się oprzeć o warunek sprawdzania przepełnienia to najpewniejszym sposobem jest badanie flagi V pod asemblerem:
repeatLoop:
inc p;
jno repeatLoop;
To jest cudo optymalizacji kompilatora w połączaniu UB (Undefined Behavior) w kodzie.
Kompilator zobaczył, że masz liczbę dodatnią, którą tylko powiększasz, więc rozkminił, że warunek p>0
jest zawsze spełniony, ergo pętla jest nieskończona.
Tutaj to pięknie widać: https://godbolt.org/z/BTTQJW
W C/C++ obowiązuje zasada "AS IF" czyli "TAK JAK" więc kompilator ma prawo robić takie przekształcania.
Równocześnie kompilator ma prawo zakładać, że kod jest pisany bez obecności UB, w twoim przypadku przekroczenie zakresu int
. Dzięki temu założeniu, kompilator też potrafi zoptymalizować kod - i wywalić kod, który jest uruchamiany tylko w wypadku UB.
Tu masz inny przykład gdzie UB prowadzi do usunięcia kodu: https://godbolt.org/z/HrqzAF
Zwróć uwagę, że if (!kot) {
zostało usunięte przez kompilator i ma();
nigdy nie jest wywołane. A przyczyna leży wyżej, żeby warunek kot->wiek > 3
miał sens kot
nie może być NULL
, a skoro nie może być, to !kot
jest zawsze fałszem.
Niestety, "signed integer overflow" jest UB, i to jest jeden z przykładów na UB które nie powinno istnieć.
Dobrze że przynajmniej warning jest klarowny.
(Przykro że są ludzie którzy ignorują warningi :P)
Ciekawostka z -O1
kompilator podszedł do optymalizacji zupełnie inaczej!
Zmienił pętle for
na stałe wyrażenie 2147483647
czyli 2^31 -1
.
A to oznacza, że rozkminił przepełnienie int-a i to że wykonają się dwie iteracje p=2147483647
oraz p=0
.