程序在在线 IDE 上表现异常
Program behaving strangely on online IDEs
我遇到了下面的 C++ 程序 (source):
#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
但是,在像 codechef 这样的在线 IDE 上,它会给出以下输出:
0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597
为什么 for
循环不在 300 处终止?此外,该程序总是在 4169
上终止。为什么 4169
而不是其他值?
您可能在 for
循环中的第 174 次迭代中调用 undefined behavior,因为最大 int
值可能是 2147483647
,但 174 * 123456789
表达式的计算结果为2148147972
这是未定义的行为,因为没有有符号整数溢出。因此,您正在观察 UB 的效果,特别是在您的案例中设置了优化标志的 GCC 编译器。编译器可能会通过发出以下警告来警告您:
warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]
删除 (-O2
) 优化标志以观察不同的结果。
"Undefined behaviour is undefined." (c)
在 codechef 上使用的编译器似乎使用以下逻辑:
- 不能发生未定义的行为。
如果 i > 173
(假设 32 位 int
s),i * 12345678
溢出并导致 UB。
- 因此,
i
永远不会超过 173
。
- 因此
i < 300
是多余的,可以用true
代替。
循环本身似乎是无限的。显然,codechef 只是在特定时间后停止程序或截断输出。
编译器可以假定不会发生未定义的行为,并且因为有符号溢出是 UB,它可以假定永远不会 i * 12345678 > INT_MAX
,因此也 i <= INT_MAX / 12345678 < 300
因此删除检查 i < 300
.
我假设在线编译器使用 GCC 或兼容的编译器。当然,也允许任何其他编译器进行相同的优化,但 GCC 文档很好地解释了它的作用:
-faggressive-loop-optimizations
This option tells the loop optimizer to use language constraints to derive bounds for the number of iterations of a loop. This assumes that loop code does not invoke undefined behavior by for example causing signed integer overflows or out-of-bound array accesses. The bounds for the number of iterations of a loop are used to guide loop unrolling and peeling and loop exit test optimizations. This option is enabled by default.
此选项仅允许根据已证明 UB 的情况进行假设。要利用这些假设,可能需要启用其他优化,例如常量折叠。
有符号整数溢出具有未定义的行为。优化器能够证明 i
的任何值大于 173 都会导致 UB,并且因为它可以假设没有 UB,所以它也可以假设 i
永远不会大于 173。它然后可以进一步证明 i < 300
始终为真,因此循环条件可以被优化掉。
Why 4169 and not some other value?
这些站点可能限制了它们显示的输出行(或字符或字节)的数量,并且碰巧共享相同的限制。
我遇到了下面的 C++ 程序 (source):
#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
但是,在像 codechef 这样的在线 IDE 上,它会给出以下输出:
0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597
为什么 for
循环不在 300 处终止?此外,该程序总是在 4169
上终止。为什么 4169
而不是其他值?
您可能在 for
循环中的第 174 次迭代中调用 undefined behavior,因为最大 int
值可能是 2147483647
,但 174 * 123456789
表达式的计算结果为2148147972
这是未定义的行为,因为没有有符号整数溢出。因此,您正在观察 UB 的效果,特别是在您的案例中设置了优化标志的 GCC 编译器。编译器可能会通过发出以下警告来警告您:
warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]
删除 (-O2
) 优化标志以观察不同的结果。
"Undefined behaviour is undefined." (c)
在 codechef 上使用的编译器似乎使用以下逻辑:
- 不能发生未定义的行为。 如果
i * 12345678
溢出并导致 UB。- 因此,
i
永远不会超过173
。 - 因此
i < 300
是多余的,可以用true
代替。
i > 173
(假设 32 位 int
s),循环本身似乎是无限的。显然,codechef 只是在特定时间后停止程序或截断输出。
编译器可以假定不会发生未定义的行为,并且因为有符号溢出是 UB,它可以假定永远不会 i * 12345678 > INT_MAX
,因此也 i <= INT_MAX / 12345678 < 300
因此删除检查 i < 300
.
我假设在线编译器使用 GCC 或兼容的编译器。当然,也允许任何其他编译器进行相同的优化,但 GCC 文档很好地解释了它的作用:
-faggressive-loop-optimizations
This option tells the loop optimizer to use language constraints to derive bounds for the number of iterations of a loop. This assumes that loop code does not invoke undefined behavior by for example causing signed integer overflows or out-of-bound array accesses. The bounds for the number of iterations of a loop are used to guide loop unrolling and peeling and loop exit test optimizations. This option is enabled by default.
此选项仅允许根据已证明 UB 的情况进行假设。要利用这些假设,可能需要启用其他优化,例如常量折叠。
有符号整数溢出具有未定义的行为。优化器能够证明 i
的任何值大于 173 都会导致 UB,并且因为它可以假设没有 UB,所以它也可以假设 i
永远不会大于 173。它然后可以进一步证明 i < 300
始终为真,因此循环条件可以被优化掉。
Why 4169 and not some other value?
这些站点可能限制了它们显示的输出行(或字符或字节)的数量,并且碰巧共享相同的限制。