程序在联机 IDE 上表现奇怪

我遇到了下面的 C + + 程序(来源) :

#include <iostream>
int main()
{
for (int i = 0; i < 300; i++)
std::cout << i << " " << i * 12345678 << std::endl;
}

它看起来像一个简单的程序,并在我的本地机器上提供正确的输出,例如:

0 0
1 12345678
2 24691356
...
297 -628300930
298 -615955252
299 -603609574

但是,在像 厨师长这样的在线 IDE 上,它提供以下输出:

0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597

为什么 for循环不在300时终止?而且这个程序总是在 4169上终止。为什么是 4169而不是其他值?

5806 次浏览

你可能在你的 for循环的第174次迭代中调用了 未定义行为,因为最大的 int值可能是 2147483647,但是 174 * 123456789表达式的计算结果是 2148147972,因为没有有符号整数溢出,所以它是未定义行为的。因此,您正在观察 UB 的效果,特别是使用在您的示例中设置了优化标志的 GCC 编译器。编译器可能会通过发出以下警告来提醒您:

warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]

删除(-O2)优化标志以观察不同的结果。

“未定义的行为是未定义的。” (c)

在 codehef 中使用的编译器似乎使用以下逻辑:

  1. 未定义的行为是不可能发生的。
  2. 如果 i > 173(假设是32位 int) ,则 i * 12345678溢出并导致 UB。
  3. 因此,i永远不能超过 173
  4. 因此 i < 300是多余的,可以用 true代替。

循环本身似乎是无限的。显然,codehef 只是在特定时间后停止程序或者截断输出。

编译器可以假设不会发生未定义行为,而且因为有符号溢出是 UB,它可以假设从来不会发生 i * 12345678 > INT_MAX,因此也可以假设是 i <= INT_MAX / 12345678 < 300,因此删除检查 i < 300

我假设在线编译器使用 GCC 或兼容编译器。当然,任何其他编译器也可以进行同样的优化,但是 GCC 文档很好地解释了它的功能:

-faggressive-loop-optimizations

此选项告诉循环优化器使用语言约束来获得循环迭代次数的界限。这里假设循环代码不会调用未定义行为,例如导致有符号整数溢出或出界数组访问。循环的迭代次数的界限用于指导循环展开、剥离和循环出口测试优化。默认情况下启用此选项。

此选项仅允许基于 UB 被证明的情况进行假设。为了利用这些假设,可能需要启用其他优化,比如常数折叠。


有符号整数溢出具有未定义的行为。优化器能够证明,任何大于173的 i值都会导致 UB,因为它可以假设没有 UB,所以它也可以假设 i永远不会大于173。然后可以进一步证明 i < 300始终为真,因此可以优化掉循环条件。

为什么是4169而不是其他值?

这些站点可能会限制它们显示的输出行(或字符或字节)的数量,并且碰巧共享相同的限制。