W C automatyczne rzutowanie typów działa na zasadzie "zwykłych konwersji arytmetycznych". W przypadku udziału w wyrażeniu liczb o różnym znaku (signed/unsigned) następuje konwersja na podstawie następujących reguł:
Przykład niebezpiecznej arytmetyki:
int a = -1; // signed unsigned int b = 1; printf("%d ", a < b); // zawsze false, ponieważ a jest rzutowane na bardzo dużą wartość unsigned
Wynik: -1, po rzutowaniu na unsigned staje się bardzo dużą liczbą dodatnią.
Co ważne, aby pamiętać:
Pytanie: Jaki wynik zwróci wyrażenie (int)(unsigned)-1?
Oczekiwana błędna odpowiedź: "-1, ponieważ -1 jest rzutowane na int."
Prawidłowa odpowiedź:
W wyrażeniu (unsigned)-1 najpierw następuje rzutowanie -1 na unsigned (na platformie 32-bitowej to 0xFFFFFFFF), a następnie z powrotem na signed int, co również zależy od implementacji, ale często to znowu będzie -1 (jeśli używany jest dwuuzupełniający). Jednak lepiej powiedzieć: Wynik zależy od standardów reprezentacji liczb signed, ale w większości implementacji otrzymasz -1.
Przykład:
int x = (int)(unsigned)-1; // x == -1 na większości platform
Historia
W przetwarzaniu łańcuchów wykorzystano funkcję do porównywania rozmiarów: jeśli długość łańcucha mogła być ujemna, program zgłaszał błąd. Jednak długość była typu size_t (unsigned) i porównanie
if(length < 0)zawsze zwraca false, co doprowadziło do nieskończonej pętli i przepełnienia pamięci.
Historia
Podczas parsowania protokołu pakiety sieciowe zawierały pola jako unsigned, podczas gdy lokalne zmienne były signed. Z powodu przepełnienia unsigned podczas przetwarzania niektórych wartości wystąpiły błędne obliczenia długości pakietów, co przyczyniło się do luki związanej z przepełnieniem bufora.
Historia
Moduł porównywania dat w logach przechowywał datę jako unsigned int, a poszukiwał zakresu dat w int. Niektóre wartości graniczne, zamiast oczekiwanego wywołania wyjątku, prowadziły do niepoprawnego filtrowania zapisów i utraty ważnych logów.