Rust在调试和发布模式下处理有符号整数溢出的方式不同。当它发生时,Rust在调试模式下会恐慌,而在发布模式下会默默地执行两个补码的包装。
据我所知,C/C将有符号整数溢出视为未定义的行为,部分原因是:
因此,如果Rust编译器确实在有符号整数方面执行与C/C编译器相同的优化,为什么The Rustonomicon声明:
无论如何,Safe Rust都不会导致未定义的行为。
或者,即使 Rust 编译器不执行此类优化,Rust 程序员仍然不希望看到有符号整数环绕。难道不能称之为“未定义的行为”吗?
问:因此,如果 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整数使用二的补码表示法,所以此指令适用于有符号整数和无符号整数。
nuw
和nsw
分别代表“无签名包装”和“无签名包装”。如果存在 nuw
和/或 nsw
关键字,则如果分别发生无符号和/或有符号溢出,则添加的结果值为毒物值。
毒值是导致未定义行为的原因。如果不存在标志,则结果定义为2的补码包装。
Q: 或者,即使Rust编译器不执行此类优化,Rust程序员也不会看到有符号整数的环绕。难道不能称之为“未定义的行为”吗?
在这个上下文中使用的“未定义行为”有一个非常具体的含义,这与这两个单词的直观英语含义不同。UB在这里特别意味着编译器可以假设永远不会发生溢出,如果发生溢出,则允许任何程序行为。这不是Rust指定的。
但是,通过算术运算符的整数溢出在 Rust 中被认为是一个错误。这是因为,正如你所说,这通常是没有预料到的。如果您有意想要包装行为,可以使用诸如 i32::wrapping_add
之类的方法。
一些额外的资源: