C++ 浮点数到整数的转换可能是未定义行为

Lobsters Hottest 新闻

摘要

一篇博文指出,C++ 中浮点数到整数的转换在值不匹配时是未定义行为,并指出微软 GSL 库的安全窄化函数 'gsl::narrow' 也存在此未定义行为,与其文档说明相悖。

<p><a href="https://lobste.rs/s/ba2yfy/c_float_int_conversion_can_be_undefined">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/07/30 05:47

# C++ 中 float 转换为 int 可能是未定义行为 来源:https://kttnr.net/blog/cpp-float-to-int-conversion-undefined-behavior/ 在 C++ 中,将 float 转换为 int 时,如果截断后的 float 无法放入目标整数类型,则该行为是未定义的。C++ 很容易让人不小心触发这种情况。许多代码在这方面都是错误的。 ```cpp void foo(float f) { int i0 = f; int i1 = int(f); int i2 = static_cast<int>(f); } ``` 这段代码不会产生任何警告,即使启用了 `-Wall` 和 `-Wextra` 也是如此。`-Wconversion` 只会对隐式转换发出警告。然而,这三种转换对于某些输入都是未定义行为。 Cppreference 的[隐式转换](https://en.cppreference.com/w/cpp/language/implicit_conversion)页面中,*浮点型到整型的转换*一节指出: > 浮点类型的纯右值可以转换为任意整数类型的纯右值。小数部分被截断,即丢弃小数部分。**如果该值无法放入目标类型,则行为是未定义的**(即使目标类型是无符号的,模运算也不适用)。 我在实际代码中多次发现这个错误。例如,在微软的 [Guidelines Support Library (GSL)](https://github.com/microsoft/GSL) 中,该库支持 [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines)。GSL 提供了一个用于安全窄化转换的函数 `gsl::narrow` (https://github.com/microsoft/GSL/blob/a75212b9f3b14162edd62d540cbf9273d5a59d20/docs/headers.md#gslnarrow): > `gsl::narrow<T>(x)` 是一个命名转型,它对窄化转换执行 `static_cast<T>(x)`,且不进行符号提升。如果参数 `x` 无法表示为目标类型 `T`,则函数会抛出异常。 我好奇他们是如何处理 float 到 int 转换的。结果发现,与文档相反,他们并没有处理这种情况。对于某些输入,`gsl::narrow` 的行为是未定义的,而不是抛出异常。我在[一条评论](https://github.com/microsoft/GSL/issues/786#issuecomment-1003717091)中指出了这一点,但被以下理由驳回: > “关于内部使用 UB:这是可以的,如果有人担心,那么在我们目标平台上使用 UB 是良性的(例如,这些类型的硬件陷阱表示不会涉及)。” 未定义行为是真实存在的,但在当前的处理器和编译器上,程序通常仍然能工作。编译器往往会选择像 x86 的 `CVTTSS2SI` 这样的指令,它会将所有不可表示的输入映射到同一个占位整数 `INT_MIN`。AArch64 有 `FCVTZS`,它会饱和并将 NaN 映射为零。程序不崩溃可能让问题看起来无伤大雅。但事实并非如此。在不同硬件上得到**不同结果**本身就是问题。更重要的是,任何被执行的未定义行为都是问题。Ralf Jung 在他的[文章](https://www.ralfj.de/blog/2019/07/14/uninit.html) *“What The Hardware Does” is not What Your Program Does* 中很好地解释了这一点。(我推荐他的博客。)当编译器碰巧应用了不同的变换时,你的代码可能突然停止工作。 正确的做法是在转换前进行边界检查。我将其实现为一个[概念验证库](https://github.com/e00E/cpp-clamp-cast/),基于 Rust 的[饱和处理方式](https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric.float-as-int)。 你也可以使用 Clang 和 GCC 的 [Undefined Behavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) 来检测未定义行为,参见 `-fsanitize=float-cast-overflow`。我建议用 UBSan 测试所有 C++ 代码。 错误的 GSL 理由已经从 issue 评论进入了[代码](https://github.com/microsoft/GSL/blob/a75212b9f3b14162edd62d540cbf9273d5a59d20/include/gsl/narrow#L47)。该问题尚未修复。

相似文章

C语言中的一切皆为未定义行为

Hacker News Top

一位经验丰富的C++开发者认为,所有非平凡的C和C++代码都包含未定义行为,使得内存安全无法实现,并质疑这些语言在现代软件开发中的持续使用。

中间浮点精度

Lobsters Hottest

本文探讨了C++代码中的中间浮点精度如何依赖于编译器设置、CPU标志和架构,尤其是在x87 FPU上,以及这如何影响性能和计算结果。

C++26:减少未定义行为

Lobsters Hottest

C++26 引入了减少未定义行为的更改,特别是使删除指向不完整类型的指针成为格式错误,从而提高了程序安全性。

C++26:标准库强化

Lobsters Hottest

C++26 引入了标准化的库强化机制,用于在运行时捕获常见的未定义行为(如越界访问)。基于 Google 的生产经验,此举仅带来 0.30% 的性能开销,同时将段错误减少了 30%。

浮点数不与自己一致

Hacker News Top

开发者发布了 `exact-poly`,这是一个使用精确整数算术而非浮点数的二维几何库,旨在消除因 IEEE 754 实现差异导致的跨平台重现性问题。