提问者:小点点

在释放模式下的安全信任中的有符号整数溢出被认为是未定义的行为吗?


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

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

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

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

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

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


共1个答案

匿名用户

问:因此,如果 Rust 编译器确实执行与 C/C 编译器相同的有符号整数优化

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

对于发布模式下的添加,Rust会发出以下LLVM指令(可以在操场上检查):

add i32 %b, %a

另一方面,叮当声将发出以下 LLVM 指令(您可以通过叮当声 -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 中被认为是一个错误。这是因为,正如你所说,这通常是没有预料到的。如果您有意想要包装行为,可以使用诸如 i32::wrapping_add之类的方法。

一些额外的资源:

  • RFC 560指定了Rust中关于整数溢出的所有内容。简而言之:调试模式下的恐慌,发布模式下的补码包装
  • 关于锈蚀中整数溢出的神话和传说。关于这个话题的博客帖子不错