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 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 <stdio.h>
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ć 👍.
