Prosta pętla for się nie kończy

Odpowiedz Nowy wątek
2019-07-16 07:44
0
#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?

Pozostało 580 znaków

2019-07-16 08:15
2

1) ten kod nie ma nic wspolnego z C++, to jest po prostu C
2) warunek pętli to Twój problem

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

edytowany 1x, ostatnio: fasadin, 2019-07-16 08:15

Pozostało 580 znaków

2019-07-16 08:26
1

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.


Pokaż pozostałe 3 komentarze
Z tą dedukcją może być różnie. Np. nie udało mi się zmusić gcc (nawet z -O3) aby tak zoptymalizował powyższy kod. Ale przy niczym się nie upieram. Jest to UB więc to jakby ucina dyskusje i przyznaję, że nie potrzebnie wyskoczyłem z przepełnieniami. - rrowniak 2019-07-16 08:45
Na debugu bedzie prawdopodobnie tak jak mowisz @rrowniak. Natomiast z optymalizacjami wersja @kg jest duzo bardziej prawdopodobna. - amd 2019-07-16 08:47
https://godbolt.org/z/rrn52X tutaj zarówno gcc jak i clang tak to interpretują - kq 2019-07-16 09:12
Co ciekawe, jeśli zmodyfikuję ten kod dodając pętlę z oryginału to już tylko clang tak to interpretuje (https://godbolt.org/z/T1XzY5). - rrowniak 2019-07-16 09:29
Tutaj pewnie zostało użyte założenie, że kod niezawierający odniesień do IO (lub czegośtam jeszcze) nie zawiera nieskończonych pętli. Generalnie terytorium UB to faktycznie słabe pole do dyskusji ;​) - kq 2019-07-16 09:32

Pozostało 580 znaków

2019-07-16 08:30
0
  1. To jest kod przykładowy w C. Adekwatny kod w C++ zachowuje się identycznie.
  2. Ależ ja wiem jak to działa, a raczej jak powinno. 0x7fffffff jest największą liczbą nieujemną typu in32. Po inkrementacji powinna nastąpić zmiana znaku i zakończenie pętli. Tak się jednak nie dzieje.
    Efekt wywołał u mnie małą konsternację, dlatego pytam na forum, bo nie wiem, czy nie zadziałały tutaj jakieś nowe ficzery kompilatora.
    Dodam jeszcze, że efekty zastosowania kompilatora 32-bitowego lub 64-bitowego są identyczne.

Pozostało 580 znaków

2019-07-16 08:33
0
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 :)

Pozostało 580 znaków

2019-07-16 08:33
4

https://stackoverflow.com/que[...]still-undefined-behavior-in-c

tak jak pisalem jest to UB i zapewne bedzie zaleznie od czynnikow jak:

  • Kompilator
  • flagi z ktorymi kompilujesz

ale nadal, jako ze to UB nie powinienes spodziewac sie jakiego kolwiek wyniku bo z tego kodu mogą wyjść nasal demons

https://stackoverflow.com/que[...]ut-signed-integer-overflow-is

edytowany 3x, ostatnio: fasadin, 2019-07-16 08:36

Pozostało 580 znaków

2019-07-16 08:33
4

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


edytowany 2x, ostatnio: Patryk27, 2019-07-16 08:34

Pozostało 580 znaków

2019-07-16 08:45
0
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 :)

edytowany 1x, ostatnio: rajszym, 2019-07-16 08:46
To nie kompilator jest przemądrzały, tylko ty piszesz błędny kod. Nie ma ŻADNEJ gwarancji że zachowanie będzie takie jak opisałeś. Równie dobrze program mógłby się wysypać na tym overflow - Shalom 2019-07-16 10:00
Strasznie poważnie tę moją dygresję potraktowałeś... Życzę miłego dnia! - rajszym 2019-07-16 20:42

Pozostało 580 znaków

2019-07-16 08:49
4

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;

"Sugeruję wyobrazić sobie Słońce widziane z orbity Merkurego, a następnie dupę tej wielkości. W takiej właśnie dupie specjalista ma teksty o wspaniałej atmosferze, pracy pełnej wyzwań i tworzeniu innowacyjnych rozwiązań. Pracuje się po to, żeby zarabiać, a z resztą specjalista sobie poradzi we własnym zakresie, nawet jeśli firma mieści się w okopie na granicy obu Korei."
-somekind,
konkretny człowiek-konkretny przekaz :]
edytowany 3x, ostatnio: MasterBLB, 2019-07-16 08:52
Nie lepiej użyć typu unsigned po prostu? - kq 2019-07-16 09:14
Niee, tu chodzi o to, aby zabronić kompilatorowi optymalizacji zmiennej przez użycie volatile. Sam się na podobny problem nadziałem przy operacjach zmiennoprzecinkowych. - MasterBLB 2019-07-16 09:16
Ale wrap-around na unsigned jest well-defined, więc nie będzie takiej optymalizacji jak dla signed. - kq 2019-07-16 09:29
W sumie, to powinniśmy zacząć od zapytania autora po cóż mu takie akrobacje. - MasterBLB 2019-07-16 10:07
Nie chodzi tutaj o akrobacje, a o sam fakt wystąpienia problemu, którym zostałem zaskoczony. Skoro znam przyczynę, to problem przestał istnieć. Rozwiązań jest bardzo wiele. - rajszym 2019-07-16 20:34

Pozostało 580 znaków

2019-07-16 10:59

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.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 3x, ostatnio: MarekR22, 2019-07-16 11:14

Pozostało 580 znaków

2019-07-17 11:07
3

Niestety, "signed integer overflow" jest UB, i to jest jeden z przykładów na UB które nie powinno istnieć.

W Ruscie to poprawili. Aczkolwiek to, że signed overflow jest UB ma pewne zalety, np. kompilator może założyć, że x + 1 > x jest zawsze prawdziwe oraz, że x * 2 / 2 == x zawsze. Może to być przydatne, zwłaszcza w C++ i szablonach. - hauleth 2019-07-17 12:09
PHP też nie powinien istnieć a ludzie w tym piszą. - PerlMonk 2019-07-17 13:38

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0