← На главную

Microsoft GSL: gsl::narrow всё ещё содержит UB при касте float во int

30.07.2026 09:59 · hackernews

В C++ преобразование float во int может быть неопределённым поведением (UB). Если после отбрасывания дробной части число не помещается в целевой целочисленный тип, программа ломается по стандарту — даже для unsigned, где принято думать про модульную арифметику. C++ позволяет написать такую конвертацию случайно, и компилятор молчит: ни -Wall, ни -Wextra не ругаются. -Wconversion ловит только неявный вариант. А вот int(f), static_cast(f) и обычное присваивание — всё это UB для некоторых значений.

Автор статьи натыкался на эту ошибку в реальном коде. Например, в Microsoft Guidelines Support Library (GSL), которая поддерживает C++ Core Guidelines. Там есть gsl::narrow — функция, которая должна безопасно сужать типы: делает static_cast, а если значение не представимо в целевом типе, бросает исключение. Однако, вопреки документации, с float-to-int она не справляется: для части входных значений gsl::narrow тоже UB.

Когда автор указал на это в комментарии, отмахнулись: дескать, UB внутри допустимо, на целевых платформах оно безобидное, аппаратных trap-представлений для этих типов нет. Формально UB существует, но на современных процессорах и компиляторах программа обычно не падает. Компиляторы выбирают инструкции вроде x86 CVTTSS2SI, которая отображает непредставимые входы на INT_MIN, или AArch64 FCVTZS, которая насыщается и превращает NaN в ноль.

То, что программа не крашится, ещё не значит, что всё в порядке. Во-первых, разные платформы дают разные результаты. Во-вторых, исполняющийся UB опасен сам по себе. Ralf Jung хорошо объясняет это в посте «What The Hardware Does» is not What Your Program Does: код может внезапно перестать работать, когда компилятор применит другое преобразование.

Правильный фикс — проверять границы до каста. Автор сделал proof-of-concept библиотеку, основанную на saturating-подходе из Rust. Также UB можно ловить с помощью Undefined Behavior Sanitizer в Clang и GCC через -fsanitize=float-cast-overflow; автор рекомендует гонять C++ код с UBSan в любом случае.

Ошибочная логика из комментария GSL перекочевала прямо в код, и проблема до сих пор не исправлена.

Читать оригинал →