W tym materiale mam kolejne ciekawe zjawisko programistyczne do pokazania 🚀. Opiszę Ci czym w programowaniu jest przekroczenie zakresu liczb całkowitych 💥! Dotyczy to liczbowych typów danych, a konkretniej ich zakresów. Pokażę jakie to przynosi konsekwencje, z czego to wynika i jak możemy się przed tym ustrzec, także zapraszam ⏩!

ZOBACZ CZYM GROZI PRZEKROCZENIE ZAKRESU LICZB CAŁKOWITYCH W PROGRAMOWANIU!

Zanim przejdę do pokazania samych skutków, wyjaśnimy sobie sam groźnie brzmiący termin 🔍.

CZYM JEST PRZEKROCZENIE ZAKRESU LICZB CAŁKOWITYCH W PROGRAMOWANIU?

Przekroczenie zakresu liczb całkowitych (ang. integer overflow) jest w programowaniu zjawiskiem polegającym na przejściu na minimalny zakres liczbowy w wyniku przekroczenia górnej granicy przedziału jaki może "zmieścić" konkretny liczbowy typ danych (np. "int") 💥. Taka sytuacja zachodzi podczas zwiększania wartości gdy dany typ liczbowy nie jest w stanie pomieścić tak dużej liczby, która wykracza poza jej maksymalny zakres 📉. To dotyczy też operacji odejmowania i wtedy to funkcjonuje na odwrót (przekroczony jest minimalny zakres i liczba "przechodzi" na maksymalną granicę).

Przekroczenie zakresu liczb całkowitych powstaje z powodu ograniczonej liczby bitów pamięci przeznaczonych na przechowywanie liczby całkowitej, a liczba tych bitów zależy od określonego typu danych jakiego użyjemy 🎯. Gdy dana liczba przechowywana w zmiennej osiągnie maksymalną (lub minimalną) granicę, kolejne zwiększenie (lub zmniejszenie) o dowolną liczbę spowoduje "odliczanie jej od nowa" i natychmiastowe przejście na liczby przeciwne 🔁. Jest to sytuacja niepożądana, która może generować późniejsze ukryte błędy przypominające bombę z opóźnionym zapłonem 💣.

Bardzo dobrym przykładem ukazującym takie konsekwencje jest słynny błąd w grze "Pac-Man" (1980), w której poziom 256 jest niemożliwy do ukończenia z powodu uszkodzonej prawej strony układu poziomu wraz z kropkami do zbierania (których zebranie jest warunkiem pomyślnego ukończenia poziomu!) 💥! Część programu odpowiedzialna za rysowanie poziomu ma problemy akurat na tym poziomie z powodu przekroczenia zakresu liczb całkowitych! Licznik numeru poziomu jest 8-bitowym typem liczbowym bez znaku (typ "unsigned") w wyniku czego, po dodaniu wartości 1 do liczby 255 następuje "przeskoczenie" do zera, co jest bezpośrednią przyczyną popsucia działania całej gry 🔥!

Przy dzisiejszych możliwościach sprzętowych rzadko narazimy się na taki przypadek, natomiast jeżeli sytuacja wymaga od nas oszczędniejszego podejścia do pamięci i korzystamy z typów danych o mniejszej liczbie bitów niż "int", to trzeba bardzo uważać i upewniać się, że przy wzrostach (lub spadkach) wartość nie przekroczy limitu od górnej czy od dolnej strony ⚠️.

Teraz zaprezentuję prosty przykład ukazujący sytuację przekroczenia zakresu liczb całkowitych 🎬!

JAK POWSTAJE PRZEKROCZENIE ZAKRESU LICZB CAŁKOWITYCH W PROGRAMOWANIU?

Cały proces wytłumaczę najpierw teoretycznie, a potem na prostym kodzie źródłowym.

TEORIA

Wyobraź sobie, że mamy zmienną typu liczbowego zdolnego do przechowywania wartości w przedziale <0; 255> (8 bitów). Ta zmienna przyjmuje wartość 254. Popatrzmy na nią z perspektywy bitowej 👇:

11111110

Limit jest ośmiobitowy i stąd po zsumowaniu potęg liczby 2 metodą opisaną przy konwersji na system binarny, górna granica wynosi 255 (to zależy czy to typ bez znaku czyli "unsigned", natomiast na razie nie patrzmy na to) 💡. Gdy do tej wartości dodamy 1, to wówczas układ bitów będzie taki 👇:

11111111

To oznacza wyczerpanie wszystkich wolnych "miejsc" jakie były dostępne w bitach, czyli wartość jest równa 255 🔥. Jeżeli teraz dodamy "jedynkę", oto co się wydarzy 👇:

00000000

Mamy przekroczenie zakresu liczb całkowitych 💥! Czyli "wróciliśmy się" do samego początku, a wartość jest równa 0 (zero). Następny wzrost spowodował "przestawienie się" licznika, który powrócił do minimalnej wartości mogącej się zmieścić w ośmiu bitach 💡.

Gdy sytuacja jest odwrotna, czyli odejmujemy będąc przy minimalnej granicy, także narażamy się na to, lecz wtedy wartość będzie równa górnej granicy (255)! Rozpatrzmy przypadek, w którym mówimy o tym samym typie danych, tylko teraz zmienna przyjmuje wartość 1. Czyli tak będą wyglądać bity tej liczby 👇:

00000001

Odejmując "jedynkę" od "jedynki" otrzymamy zero i wtedy bity będą układać się w same zera 👇:

00000000

Gdy jeszcze raz wykonamy takie samo odejmowanie, mamy podobną sytuację 👇:

11111111

Wyszliśmy poza przedział tylko od drugiej strony, więc liczba "przeskoczyła" do górnej granicy i to jest przekroczenie zakresu liczb całkowitych 💥!

Przekroczenie zakresu liczb całkowitych w programowaniu

Przekroczenie zakresu liczb całkowitych to zjawisko programistyczne, w którym liczba "przechodzi" na drugi koniec zakresu liczb jakie może posiadać określony typ danych spowodowane przekroczeniem przedziału liczbowego powstałego z ograniczonej liczby bitów.

Pokazałem całe zagadnienie na typie bez znaku ("unsigned"), czyli na takim typie liczbowym, który posiada dwukrotnie większy zakres dodatni, natomiast nie posiada możliwości przechowywania w nim liczb ujemnych (minimalną wartością jest zero). Natomiast to obejmuje także liczbowe typy danych ze znakiem ("signed"), czyli zakresy po obu stronach są rozłożone prawie identycznie, a konkretniej to przyjmują wartości z następującego przedziału 👇:

<-2x; 2x - 1> : x = liczba bitów typu danych

Tak wygląda przekroczenie zakresu liczb całkowitych teoretycznie, a teraz zobaczmy przykład na kodzie źródłowym 🚀!

KOD ŹRÓDŁOWY

Oto prosty przykład kodu źródłowego w języku C, który doskonale pokazuje przekroczenie zakresu liczb całkowitych poprzez dodanie wartości 1 do licznika będącego już na maksymalnej granicy liczb dodatnich 👇:

#include &lt;stdio.h&gt;

int main(void)
{
	unsigned char smallNumber = 255;

	printf("%d\n", smallNumber);

	smallNumber++;

	printf("%d\n", smallNumber);

	return 0;
}

Na samym początku funkcji "main" tworzymy sobie zmienną odpowiadającą zakresowi ośmiobitowemu od 0 do 255. W języku C taki zakres posiada typ "unsigned char" 🎯. Przypisujemy jej wartość najwyższą jaką może zmieścić, czyli 255. Następnie mamy wywołanie funkcji "printf" celem ujrzenia jej bieżącej wartości 👀. Specyfikator "%d", choć jest skierowany głównie do typu "int", to bez problemu zadziała też dla typu "char" i pomimo, że jego głównym zastosowaniem jest przechowywanie kodów znaków w systemie ASCII, to można też przypisywać liczby całkowite ☑️.

Po pierwszym wyświetleniu wartości licznika, zwiększamy jej wartość o jeden poprzez inkrementację (to są te dwa plusy zaraz po nazwie). W tym miejscu dochodzi do "ukrytego" przekroczenia zakresu liczb całkowitych 🔥! Kompilator nie da żadnego znaku w formie ostrzeżenia, że w tym i tym miejscu zachodzi ryzyko wystąpienia takiej sytuacji ❌! Dlatego to jest bardzo niebezpieczne!

Ostatnią instrukcją programu jest to samo wywołanie funkcji "printf", tak jak poprzednio.

Gdy skompilujesz sobie ten program i uruchomisz go, to ujrzysz w konsoli dwie wartości licznika: pierwsza sprzed modyfikacji i druga po modyfikacji. Pierwsza będzie równa 255, a druga będzie równa 0 (zero)!

To jest koniec prezentacji w kodzie źródłowym!

JAK MOŻNA UNIKNĄĆ PRZEKROCZENIA ZAKRESU LICZB CAŁKOWITYCH W PROGRAMOWANIU?

Jeżeli nie jesteś w sytuacji, w której pojemność pamięci jest przytłaczająco ograniczona (a może tak być w systemach wbudowanych), to po prostu korzystaj z liczbowych typów danych o bardzo dużych zakresach liczbowych i wtedy ryzyko narażenia się spadnie do minimum 😄! W dzisiejszych czasach, 99% wartości całkowitoliczbowych w programach, korzysta z najbardziej podstawowego typu jakim jest "int" 😉!


I tak się kończy zagadnienie przekroczenia zakresu liczb całkowitych ✅. Mam nadzieję, że udało mi się prostym językiem wyjaśnić cały temat i będziesz już wiedzieć z czego to powstaje oraz jak się tego wystrzegać 👍.

PODOBNE ARTYKUŁY