ostatnio widzę, że dużo na bugzilli gcc ludzie zgłaszają dużo
błędów w stylu "moja aplikacja na gcc4 robi kuku, a na 3.x było ok".
bardzo często są ta programy po prostu błędne podług standardów,
stąd też postanowiłem nieco przybliżyć ten temat deweloperom pld,
by szybciej i łatwiej w Th naprawiać niespodziewane zachowanie
aplikacji, tudzież kuku zwane potocznie GPF-em :)

rozważmy pierwszy załączony przykład:

$ g++ signed_overflow_1.cpp; ./a.out

b      = (0x80000000) -2147483648
10 - b = (0x8000000a) -2147483638 < 0 is true.
b - 10 = (0x7ffffff6) +2147483638 < 0 is true.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    tu zapewne niektórym zapalił się pytajnik
i myslą sobie jak do cholery to się mogło stać :)

pozwole sobię zacytować standard:

[ cite C++ standard / $5.5 ]

if during the evaluation of an expression the result isn't mathematically
defined nor in the range of representable values for its type the behaviuor
is undefined, unless such an expression is a constant expression, in which
case the program is ill-formed. (...)

[ /cite ]

w naszym przypadku b - 10, to 0xff(...)7ffffff6, co nie mieści się
w 32 bitach typu integer i kwalifikuje się na overflow oraz niezdefiniowane
zachowanie, czyli wyniki z grubsza zależne od natężenia plam na słońcu
i szumów w krzemie :)

poprzednie wersje gcc oraz inne kompilatory zlewają nieco w.w. punkt
standardu i po cichu "zawijają" takie wyniki na granicy rozmiaru typu.
stare (błedne zachowanie) można przywrócić za pomocą opcji -fwrapv.

$ g++ signed_overflow_1.cpp -fwrapv; ./a.out

b      = (0x80000000) -2147483648
10 - b = (0x8000000a) -2147483638 < 0 is true.
b - 10 = (0x7ffffff6) +2147483638 < 0 is false.

nowsze wersje gcc są już bardziej zbieżne ze standardem
i robią z niego użytek.

skompilujmy dla przykładu drugi załącznik, który nie ma już UB,
ale produkuje rózne wyniki:

$ g++ signed_overflow_2.cpp -O2
$ ./a.out

 foo(1) = 0
 foo(2147483647) = 0

0000000000000000 <foo(int)>:
   0:   31 c0                   xor    %eax,%eax
   2:   c3                      retq

miodzio i zgodnie z matematyką, bo nigdy i + 1 nie będzie mniejsze od i.
jednak, gdy skompilujemy ów fragment z -fwrapv, to kompilator podda
matematykę rzeczywistości i dostaniemy:

$ g++ signed_overflow_2.cpp -O2 -fwrapv
$ ./a.out
 foo(1) = 0
 foo(2147483647) = 1

0000000000000000 <foo(int)>:
   0:   8d 47 01                lea    0x1(%rdi),%eax
   3:   39 c7                   cmp    %eax,%edi
   5:   0f 9f c0                setg   %al
   8:   0f b6 c0                movzbl %al,%eax
   b:   c3                      retq

jak widać badanie signed overflow może dać duże korzyści przy
optymalizacji kodu, ale również może wpędzić nas w "ukryte" błędy,
bądź niezdefiniowane zachowanie w miejscach, gdzie polega się na
zabawach z bitami.
#include <cstdio>
int main()
{
        int b = -2147483648;	// -2 ^ 31
	std::printf( "b      = (0x%08x) %+10d\n", b, b );
        if ( ( 10 - b ) < 0 )
    		std::printf( "10 - b = (0x%08x) %+10d < 0 is true.\n", 10 - b, 10 - b );
	else
    		std::printf( "10 - b = (0x%08x) %+10d < 0 is false.\n", 10 - b, 10 - b );
        if ( ( b - 10 ) < 0 )
    		std::printf( "b - 10 = (0x%08x) %+10d < 0 is true.\n", b - 10, b - 10 );
	else
    		std::printf( "b - 10 = (0x%08x) %+10d < 0 is false.\n", b - 10, b - 10 );
        return 0;
}
bool foo( int i )
{
	return ( ( i + 1 ) < i );
}

#include <cstdio>
int main()
{
	printf(" foo(%d) = %d\n", 1, foo( 1 ) );
	printf(" foo(%d) = %d\n", 0x7fffffff, foo( 0x7fffffff ) );
	return 0;
}
_______________________________________________
pld-devel-pl mailing list
[email protected]
http://lists.pld-linux.org/mailman/listinfo/pld-devel-pl

Odpowiedź listem elektroniczym