释放模式下安全 Rust 中的有符号整数溢出是否被视为未定义行为?

Zhi*_* Ma 5 integer-overflow rust llvm-codegen

Rust 在调试和发布模式下以不同的方式处理有符号整数溢出。当它发生时,Rust 在调试模式下会发生恐慌,而在发布模式下默默地执行二进制补码包装。

据我所知,C/C++ 将有符号整数溢出视为未定义行为,部分原因是:

  1. 在 C 标准化的那个时候,表示有符号整数的不同底层体系结构,例如补码,可能仍在某处使用。编译器无法假设硬件中如何处理溢出。
  2. 后来的编译器因此做出假设,例如两个正整数之和也必须为正数才能生成优化的机器代码。

因此,如果 Rust 编译器确实在有符号整数方面执行与 C/C++ 编译器相同类型的优化,那么为什么The Rustonomicon指出:

无论如何,Safe Rust 不会导致未定义行为。

或者即使 Rust 编译器不执行这样的优化,Rust 程序员仍然不希望看到有符号整数环绕。不能称为“未定义行为”吗?

Luk*_*odt 9

问:那么,如果 Rust 编译器确实对有符号整数执行了与 C/C++ 编译器相同类型的优化

锈没有。因为,正如您所注意到的,它无法执行这些优化,因为整数溢出是明确定义的。

对于发布模式的添加,Rust 将发出以下 LLVM 指令(您可以查看Playground):

add i32 %b, %a
Run Code Online (Sandbox Code Playgroud)

另一方面,clang 将发出以下 LLVM 指令(您可以通过查看clang -S -emit-llvm add.c):

add nsw i32 %6, %8
Run Code Online (Sandbox Code Playgroud)

不同之处在于nsw(无签名换行)标志。正如LLVM 参考中add所指定

如果和有无符号溢出,返回的结果是数学结果模 2n,其中 n 是结果的位宽。

由于 LLVM 整数使用二进制补码表示,因此该指令适用于有符号和无符号整数。

nuw并分别nsw代表“No Unsigned Wrap”和“No Signed Wrap”。如果nuw和/或nsw关键字存在,并且分别发生无符号和/或有符号溢出,则加法的结果值是毒值。

毒值是导致未定义行为的原因。如果标志不存在,则结果很好地定义为 2 的补码包装。


问:或者即使 Rust 编译器不执行这样的优化,Rust 程序员仍然不希望看到有符号整数环绕。不能称为“未定义行为”吗?

在此上下文中使用的“未定义行为”具有非常具体的含义,与这两个词的直观英文含义不同。UB 在这里具体意味着编译器可以假设永远不会发生溢出,并且如果发生溢出,则允许任何程序行为。这不是 Rust 规定的。

然而,通过算术运算符的整数溢出认为是 Rust 中的一个错误。那是因为,正如你所说,它通常是无法预料的。如果您有意想要包装行为,可以使用诸如i32::wrapping_add.


一些额外的资源:

  • 当 C 标准使用术语“未定义行为”时,它的意思无非是“标准”没有强加任何要求;该标准的作者明确表示,UB“确定了符合语言扩展的领域”。该标准将允许编译器在真正有用的情况下做出您所描述的推论,但不会尝试判断它们是否有用,也不会在它们比无用更糟糕的情况下禁止它们。相反,它希望编译器编写者比委员会更了解客户的需求。 (4认同)