В C автоматическое приведение типов работает по принципу "usual arithmetic conversions". При участии в выражении объявленных чисел с разным знаком (signed/unsigned), происходит преобразование следующих правил:
Пример опасной арифметики:
int a = -1; // signed unsigned int b = 1; printf("%d ", a < b); // всегда false, т.к. a преобразуется к очень большому unsigned
Результат: -1, будучи приведён к unsigned, становится очень большим положительным числом.
Что важно помнить:
Вопрос: Какой результат вернёт выражение (int)(unsigned)-1?
Ожидаемо неверный ответ: "-1, ведь -1 и преобразуем к int."
Правильный ответ:
В выражении (unsigned)-1 сначала происходит приведение -1 к unsigned (на платформе с 32 битами это 0xFFFFFFFF), затем обратно к signed int, что также зависит от реализации, но часто это снова -1 (если используется two's complement). Однако, правильнее сказать: Результат зависит от стандартов представления signed чисел, но в большинстве реализаций получится -1.
Пример:
int x = (int)(unsigned)-1; // x == -1 на большинстве платформ
История
В обработчике строк использовали функцию сравнения размера: если длина строки может быть отрицательной, то программа сообщала об ошибке. Однако, длина была типа size_t (unsigned), и сравнение кода
if(length < 0)всегда выдаёт false, что привело к бесконечному циклу и переполнению памяти.
История
При парсинге протокола сетевые пакеты содержали поля как unsigned, а локальные переменные были signed. Из-за переполнения unsigned при обработке некоторых значений возникли неверные расчёты длины пакета, что вылилось в уязвимость переполнения буфера.
История
Модуль сравнения дат в логах хранил дату как unsigned int, а искал диапазон даты в int. Некоторые граничные значения, вместо ожидаемого срабатывания исключения, приводили к некорректной фильтрации записей и потере важных логов.