fn main() {
let num: u8 = 255;
let num2: u8 = num + 1;
println!("{}, {}", num, num2);
}
当$ cargo build --release
时,此代码不会产生编译错误。$ cargo run
运行时出错
线程'main'在'尝试添加溢出'时恐慌,src/main.rs:3:20注意:使用
RUST_BACKTRACE=1
环境变量显示回溯
这是好的。但我不明白的是下面的情况。当我删除println行时,它会产生编译错误。
fn main() {
let num: u8 = 255;
let num2: u8 = num + 1;
}
$ cargo build --release
error: this arithmetic operation will overflow
--> src/main.rs:3:20
|
3 | let num2: u8 = num + 1;
| ^^^^^^^ attempt to compute `u8::MAX + 1_u8`, which would overflow
|
= note: `#[deny(arithmetic_overflow)]` on by default
为什么整数溢出有时会导致编译错误或运行时错误?
这将是一个编译时的错误,当编译器可以证明它会在运行时溢出,这发生在你删除println
,因为num
可以很容易地被内联(它只在一个地方使用无论如何),但是println
很难优化,因为它引用了它的参数,并且不容易证明不同的地址对它没有影响(考虑也有一个fmt::Pointer
),所有这些都导致了这样一个事实,即证明num
不能那么容易内联的第一种情况并不那么容易。
作为参考,这里是每个变量中第一个和第二个变量的多个表示形式,您可以看到,在其中一个变量已经被u8::MAX
替换:
- 没有
println
;[...] bb0: { _1 = const u8::MAX; // scope 0 at plain_overflow.rs:2:19: 2:22 _3 = const u8::MAX; // scope 1 at plain_overflow.rs:3:20: 3:23 _4 = CheckedAdd(_3, const 1_u8); // scope 1 at plain_overflow.rs:3:20: 3:27 assert(!move (_4.1: bool), "attempt to compute `{} + {}`, which would overflow", move _3, const 1_u8) -> bb1; // scope 1 at main.rs:3:20: 3:27 } [...]
- with
println
:[...] bb0: { _1 = const u8::MAX; // scope 0 at with_print.rs:2:19: 2:22 _3 = _1; // scope 1 at with_print.rs:3:20: 3:23 _4 = CheckedAdd(_3, const 1_u8); // scope 1 at with_print.rs:3:20: 3:27 assert(!move (_4.1: bool), "attempt to compute `{} + {}`, which would overflow", move _3, const 1_u8) -> bb1; // scope 1 at with_print.rs:3:20: 3:27 } [...]
其中_1
、_3
和_4
对应num
,num2
所在行的num
的值以及num
和1
的检查加法的结果。
经过更多的实验,不是println
,而是仅仅引用了num
编译器警告是由linting产生的,可能很难在所有情况下静态检查算术溢出。在这种情况下,println!()
在某种程度上使编译器难以检测溢出。我认为这可能值得报告一个bug?
同样,代码产生运行时错误的原因是您没有在release
模式下运行它。尝试运行与cargo run --release
相同的代码,然后您将得到输出
255, 0
这是因为,在release
模式下,Rust不包括对导致恐慌的算术溢出的检查。相反,如果发生溢出,Rust执行two的补码包装。