在 Rust 的 release 模式中,有符号整数溢出被视为未定义行为吗?

10
Rust在Debug模式和Release模式下对待带符号整数溢出的方式不同。当发生溢出时,Rust会在 Debug 模式下 panic,而在 Release 模式下则静默地进行二进制补码包装。
据我所知,C/C++将有符号整数溢出视为未定义行为的部分原因是:
1. 在 C 标准化时期,使用不同基础结构来表示带符号整数(如一的补码)的底层架构可能仍然存在于某些地方。编译器不能假设溢出是如何在硬件上处理的。
2. 后来的编译器就会做出假设,例如两个正整数的和也必须是正数,以生成优化的机器代码。
因此,如果 Rust 编译器执行与 C/C++ 编译器相同类型的关于有符号整数的优化,为什么 《Rustonomicon》书中会说:
“无论如何,安全的 Rust 都不会导致未定义行为。”
即使 Rust 编译器不执行这种优化,Rust 程序员仍然不预期看到带符号整数的包装。这难道不能被称为 “未定义行为” 吗?

我认为第一点的假设有一个微妙的错误:编译器可以知道硬件如何处理溢出。然而,C代码不能,因为它可以在具有不同处理方式的硬件上运行。 - EFraim
1个回答

19
不是。在安全的Rust中,整数溢出在发布模式下被定义为2的补码环绕。在调试模式下,会触发panic。
问:所以如果Rust编译器在有符号整数方面执行与C/C++编译器相同类型的优化,对吗?
答:Rust并不会。因为正如您所注意到的,它无法执行这些优化,因为整数溢出是明确定义的。
在发布模式下,对于加法操作,Rust将生成以下LLVM指令(您可以在Playground上进行检查)。
add i32 %b, %a

另一方面,clang将会生成以下LLVM指令(您可以通过clang -S -emit-llvm add.c进行检查):
add nsw i32 %6, %8

区别在于nsw(无符号溢出)标志。如LLVM参考文档中所述关于add的说明

如果求和时发生无符号溢出,返回的结果是数学结果对2n取模,其中n是结果的位宽。

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

nuwnsw分别代表“无无符号溢出”和“无有符号溢出”。如果存在nuw和/或nsw关键字,则如果发生无符号和/或有符号溢出,加法的结果值将成为毒值。

毒值导致未定义行为。如果没有这些标志,结果将以2的补码形式定义。


Q: 即使Rust编译器不执行这样的优化,Rust程序员仍然不希望看到有符号整数溢出。这不能被称为"未定义行为"吗?
"未定义行为"在这个上下文中有一个非常具体的含义,与两个词的直观英语含义不同。在这里,UB特指编译器可以假设永远不会发生溢出,并且如果发生溢出,任何程序行为都是允许的。这不是Rust所规定的。
然而,在Rust中,通过算术运算符导致的整数溢出被认为是一个bug。这是因为,正如你所说,通常不会预料到这种情况。如果你有意想要溢出行为,可以使用诸如i32::wrapping_add等方法。
一些额外的资源:

4
当C标准使用“未定义行为”这个术语时,它的意思只是说标准没有强制性规定;标准的作者明确表示UB“标识符合语言扩展的领域”。如果编译器推断在某些情况下真正有用,标准允许这样做,但不试图判断它们是否有用,也不会禁止它们在比无用更糟糕的情况下使用。相反,它期望编译器编写者比委员会更了解客户需求。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接