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

Is signed integer overflow in safe Rust in release mode considered as undefined behavior?

Rust 在调试和发布模式下以不同方式处理有符号整数溢出。发生这种情况时,Rust 在调试模式下会崩溃,而在发布模式下会默默地执行二进制补码包装。

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

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

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

No matter what, Safe Rust can't cause Undefined Behavior.

或者即使 Rust 编译器不执行此类优化,Rust 程序员仍然不会期望看到有符号整数回绕。不能叫"undefined behavior"吗?

Q: So if Rust compilers do perform the same kind of optimization as C/C++ compilers regarding signed integers

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

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

add i32 %b, %a

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

add nsw i32 %6, %8

不同之处在于 nsw(无签名换行)标志。按照指定 in the LLVM reference about add:

If the sum has unsigned overflow, the result returned is the mathematical result modulo 2n, where n is the bit width of the result.

Because LLVM integers use a two’s complement representation, this instruction is appropriate for both signed and unsigned integers.

nuw and nsw stand for “No Unsigned Wrap” and “No Signed Wrap”, respectively. If the nuw and/or nsw keywords are present, the result value of the add is a poison value if unsigned and/or signed overflow, respectively, occurs.

有害值会导致未定义的行为。如果标志不存在,则结果明确定义为 2 的补码环绕。


Q: Or even if Rust compilers do not perform such optimization, Rust programmers still do not anticipate seeing a signed integer wrapping around. Can't it be called "undefined behavior"?

在此上下文中使用的

"Undefined behavior" 具有非常具体的含义,不同于这两个词的直观英语含义。 UB 在这里特别意味着编译器可以假设溢出永远不会发生,并且 if 会发生溢出,任何程序行为都是允许的。这不是 Rust 指定的内容。

然而,通过算术运算符 的整数溢出是 被认为是 Rust 中的一个错误。那是因为,正如您所说,通常不会预料到。如果你有意想要包装行为,有i32::wrapping_add等方法。


一些额外的资源: