W języku C operacje arytmetyczne na typach całkowitych mogą prowadzić do przepełnienia (overflow), gdy wynik wychodzi poza dozwolony zakres reprezentacji typu, na przykład int lub unsigned int. Specyfika zachowania podczas przepełnienia jest określona przez standardy języka.
Przepełnienie z typem znakowym (signed overflow) prowadzi do undefined behavior, co oznacza, że kompilator ma prawo do wykonania dowolnych działań: zignorować błąd, wygenerować wyjątek lub pozostawić nieprzewidywalny wynik. Dla typów bez znaku (unsigned) zgodnie ze standardem C zachowanie podczas przepełnienia jest określone: następuje reset modułowy rozmiaru typu (wraparound).
Dla liczb unsigned wynik przepełnienia jest łatwy do przewidzenia, na przykład UINT_MAX + 1 == 0. Dla liczb signed - zaleca się przed operacjami sprawdzić granice typu za pomocą makr z <limits.h> lub używać narzędzi analiz statycznych. Nowoczesne kompilatory i narzędzia mogą wykrywać potencjalne przepełnienia.
Przykład kodu:
#include <stdio.h> #include <limits.h> int add_with_check(int a, int b) { if (a > 0 && b > INT_MAX - a) { printf("Wystąpi przepełnienie! "); return -1; } return a + b; } int main() { int x = INT_MAX, y = 1; printf("Wynik: %d ", add_with_check(x, y)); unsigned int ux = UINT_MAX; printf("Przepełnienie unsigned: %u ", ux + 1); return 0; }
Kluczowe cechy:
<limits.h> do pobrania rozmiarów typówCzy przepełnienie typu unsigned jest błędem?
Nie, to zachowanie jest określone przez standard i jest równoważne resetowi modułowemu. Na przykład, (unsigned int)UINT_MAX + 1 == 0 zawsze jest prawdą.
Czy można polegać na tym, że przy przepełnieniu int wynik po prostu "przejedzie" przez INT_MIN?
Nie, takie zachowanie nie jest gwarantowane i nie jest ustandaryzowane, to undefined behavior. Może spowodować awarię, dać niepoprawną (różną na różnych platformach) wartość lub być optymalizowane przez kompilator w nieprzewidywalny sposób.
Czy można zakładać, że int zawsze jest w reprezentacji dwóch uzupełnień?
Chociaż nowoczesny sprzęt praktycznie zawsze używa „two's complement” do reprezentacji signed int, język C standardowo tego nie wymaga, więc kod z przepełnieniem nie będzie przenośny.
Dodawanie liczb int bez sprawdzania granic — przepełnienie przy dużych danych prowadzi do niepoprawnych obliczeń.
Zalety:
Wady:
Przed wszystkimi operacjami arytmetycznymi wykonywane jest sprawdzenie na przepełnienie za pomocą makr i funkcji. Użycie unsigned tam, gdzie wraparound jest dozwolone.
Zalety:
Wady: