将浮点数转换为整数可能导致未定义行为。
C++ float-to-int conversion can be undefined behavior

原始链接: https://kttnr.net/blog/cpp-float-to-int-conversion-undefined-behavior/

在 C++ 中,将浮点数转换为整数时,如果该值超出了目标类型的范围,会导致**未定义行为 (UB)**。这种情况在使用隐式转换、`int()` 或 `static_cast()` 时都会发生,但编译器通常不会对此发出警告。 许多开发者(包括 Guidelines Support Library (GSL) 的维护者)错误地认为这无关紧要,因为现代硬件(如 x86 或 AArch64)通常会产生可预测的结果,而不会导致崩溃。然而,依赖于特定硬件的行为是非常危险的;编译器可能会进行优化,从而导致代码出现意外、不可移植或损坏的情况。 作者强调,“硬件的行为”并不等同于 C++ 定义的行为。为了正确处理这些转换,必须在转换前进行显式的边界检查。或者,也可以在测试过程中使用 UBSan (`-fsanitize=float-cast-overflow`) 等工具来检测此类情况。尽管存在风险,许多项目(包括 GSL)仍然忽视这一漏洞,错误地将其辩解为“良性”未定义行为。开发者应转而采用更安全、经过边界检查的方法,以确保代码的可靠性和可移植性。

Hacker News 上的一场讨论强调了对 C++ 浮点数转整数(float-to-int)转换的担忧,这种操作在微软指南支持库(GSL)中会触发未定义行为(UB)。 Herb Sutter 最初为这种做法辩护,认为在 GSL 针对的平台上,这种 UB 是“良性”的,不会触发硬件陷阱。然而,批评者认为这种做法很危险,因为编译器可能会利用对 UB 的假设来优化代码,从而导致不可预知的结果。此外,尽管 GSL 旨在实现跨平台(支持 GCC、Clang 和 MSVC),但依赖特定的编译器行为会削弱库的可移植性及代码的语义完整性。 这场对话凸显了业界关于 C++ 的更广泛争论。一些开发者认为,如果编译器供应商能保证在特定架构下产生安全输出,那么某些 UB 是可以接受的;而另一些人则坚持认为,UB 本质上是有缺陷的,因为它会使程序逻辑失效。在社区压力下,GSL 维护者已表示正在重新审视该问题,试图摆脱对 UB 的依赖。这也反映出一种期望,即未来的 C++ 标准(如 C++29)能将此类情况重新分类为定义明确或“错误”行为,而非严格将其留作未定义行为。
相关文章

原文

Converting a float to an int in C++ is undefined behavior when the truncated float does not fit into the destination integer. C++ makes it easy to do this accidentally. Much code gets this wrong.

void foo(float f) {
    int i0 = f;
    int i1 = int(f);
    int i2 = static_cast<int>(f);
}

This code does not generate any warnings, not even with -Wall and -Wextra. -Wconversion only warns about the implicit conversion. Yet each of the three conversions is undefined behavior for some inputs.

Cppreference's implicit conversion page, section Floating-integral conversions, states:

A prvalue of floating-point type can be converted to a prvalue of any integer type. The fractional part is truncated, that is, the fractional part is discarded. If the value cannot fit into the destination type, the behavior is undefined (even when the destination type is unsigned, modulo arithmetic does not apply).

I have seen this mistake many times in the wild. For example, in Microsoft's Guidelines Support Library, which supports the C++ Core Guidelines.

GSL provides a function for safe narrowing conversions, gsl::narrow:

gsl::narrow<T>(x) is a named cast that does a static_cast<T>(x) for narrowing conversions with no signedness promotions. If the argument x cannot be represented in the target type T, then the function throws.

I was curious how they addressed float-to-int conversion. It turns out, contrary to the docs, that they do not address it. gsl::narrow is undefined behavior for some inputs. I pointed this out in a comment and was dismissed with the following reasoning:

Regarding the use of UB internally: It's okay and if anyone is worried about it the use of UB is benign on the platforms we target (e.g., they don't involve hitting any hardware trap representations for these types).

The undefined behavior is real, but with current processors and compilers, the program usually works anyway. Compilers tend to pick an instruction like x86's CVTTSS2SI, which maps every unrepresentable input to the same placeholder integer INT_MIN. AArch64 has FCVTZS, which saturates and maps NaN to zero.

Your program not crashing can make the problem seem benign. It is not. Different results on different hardware are problematic. More importantly, any undefined behavior that executes is problematic. Ralf Jung explains this well in his post "What The Hardware Does" is not What Your Program Does. (I recommend his blog.) Your code could suddenly stop working when the compiler happens to apply a different transformation.

The correct fix is to bounds check before casting. I turned this into a proof of concept library based on Rust's saturating approach.

You can also detect the undefined behavior with Clang's and GCC's Undefined Behavior Sanitizer, see -fsanitize=float-cast-overflow. I recommend testing all C++ code with UBSan anyway.

The faulty GSL reasoning has made it from the issue comment into the code. The problem has not been fixed.

created , updated

联系我们 contact @ memedata.com