Prosta pętla for się nie kończy

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?

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

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.

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.
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 :)

4

https://stackoverflow.com/questions/16188263/is-signed-integer-overflow-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/questions/18195715/why-is-unsigned-integer-overflow-defined-behavior-but-signed-integer-overflow-is

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

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 :)

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;
6

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.

3

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

0

Dobrze że przynajmniej warning jest klarowny.
(Przykro że są ludzie którzy ignorują warningi :P)

1

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.

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