C ++ while循环优化不能正常工作
我有这段代码:
#include int main(int argc, const char** argv) { int a = argv[0][0]; int b = argv[0][1]; while ((a >= 0) && (a < b)) { printf("a = %d\n", a); a++; } return 0; }
我用gcc-4.5 -02 -Wstrict-overflow=5
编译它。
编译器向我大吼大叫warning: assuming signed overflow does not occur when changing X +- C1 cmp C2 to X cmp C1 +- C2
这究竟是什么意思?
如果我是正确的,这个循环永远不会导致溢出,因为要增加a,它必须小于另一个整数。 如果它更大,则循环终止。
任何人都可以向我解释这种行为吗?
C ++标准规定,如果有符号整数计算产生的结果超出了该类型的可表示范围,则行为未定义。 整数溢出是UB。 一旦UB发生,实施可以随意做任何事情。
许多编译器对UB不会发生的明确假设进行优化。 [或者如果确实如此,代码可能是错误的,但这是你的问题!]
此编译器通知您它正在将此类优化应用于计算,在该计算中,无法通过分析代码确定UB未发生。
您的选择一般是:
- 让自己满意UB不会发生,并忽略警告。
- 允许UB发生并承担后果。
- 重写代码,以便UB真的不会发生,编译器知道它不会发生,并且警告应该消失。
我会推荐最后一个选项。 a
和b
上a
简单范围测试应该足够好。
我的猜测是编译器发出此错误,因为循环处理完全未知的值,并且它无法很好地分析数据流以确定UB是否可能发生。
我们凭借优越的推理能力可以说服自己UB不会发生,所以我们可以忽略错误。 事实上,仔细阅读错误信息可能会让我们询问它是否相关。 这两个常数值C1
和C2
哪里?
我们可能还会注意到a
永远不会消极,所以为什么循环中的测试呢? 我可能会重写代码以抑制错误,(但从经验来看,这可能是一种弄巧成拙的练习)。 试试这个,看看会发生什么(并避免不必要的括号杂乱):
if (a >= 0) { while (a < b) { ... ++a; } }
编译器正在进行优化以将a + 1 < b
转换a < b - 1
。 但是,如果b
是INT_MIN
那么这将是下溢,这是行为的变化。 这是它的警告。
当然,您可能会说这是不可能的,但编译器的资源有限,无法对数据路径进行深入分析。
添加b >= 0
的检查可以解决问题。
编辑 :另一种可能性是它将a >= 0
移动到循环外部,因为(假设没有溢出)它永远不会改变。 同样,该假设可能对所有输入都无效(即,如果b
为负)。 您需要检查最终assembly以查看它实际执行的操作。
编译器警告你的是它假设在原始代码中没有发生签名溢出 。
警告并不意味着“我即将编写一个可能引入溢出的优化。”
换句话说,如果您的程序依赖于溢出(即,不是高度可移植的),那么编译器正在进行的优化可能会改变其行为。 (因此,请自行validation此代码不依赖于溢出)。
例如,如果你有“a + b> c”,并且你依赖于这个测试在+ b算术包围时失败(典型的二进制补码行为),那么如果这恰好被代数转换为“a> c – b“,那么它可能会成功,因为c – b可能不会溢出,并产生一个小于a的值。
注意:只有C程序可以调用未定义的行为。 当编译器不正确时,它们是“不合格的”。 如果编译器符合(C标准)符合代码的错误,则编译器只能是不符合(对C标准)。 改变正确的可移植行为的优化是不合格的优化。