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