我遇到过以下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
而不是其他值?
答案 0 :(得分:102)
我将假设在线编译器使用GCC或兼容的编译器。当然,任何其他编译器也允许进行相同的优化,但GCC文档很好地解释了它的作用:
-faggressive-loop-optimizations
此选项告诉循环优化器使用语言约束来派生循环迭代次数的边界。这假设循环代码不会通过例如导致有符号整数溢出或超出范围的数组访问来调用未定义的行为。循环迭代次数的界限用于指导循环展开和剥离以及循环退出测试优化。默认情况下启用此选项。
此选项仅允许根据证明UB的情况做出假设。要利用这些假设,可能需要启用其他优化,例如常量折叠。
有符号整数溢出具有未定义的行为。优化器能够证明任何大于173的i
值都会导致UB,并且因为它可以假设没有UB,所以它也可以假设i
永远不会大于173。然后可以进一步证明i < 300
始终为真,因此可以优化循环条件。
为什么4169而不是其他值?
这些网站可能会限制它们显示的输出行数(或字符或字节数)并碰巧共享相同的限制。
答案 1 :(得分:33)
&#34;未定义未定义的行为。&#34; (c)
codechef上使用的编译器似乎使用以下逻辑:
import java.math.BigInteger;
import java.util.Scanner;
public class Main{
// Returns Factorial of N
static BigInteger factorial(int N){
// Initialize result
BigInteger f = new BigInteger("1"); // Or BigInteger.ONE
// Multiply f with 2, 3, ...N
for (int i = 2; i <= N; i++)
f = f.multiply(BigInteger.valueOf(i));
return f;
}
// Driver method
public static void main(String args[]) throws Exception
{
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
BigInteger a = (factorial(n));
BigInteger b, c;
c = new BigInteger("10");
b = a.mod(c);
System.out.println(b);
System.out.println(a);
}
}
溢出,如果i * 12345678
(假设32位i > 173
s),则会产生UB。int
永远不会超过i
。173
是多余的,可以替换为i < 300
。循环本身似乎是无限的。显然,codechef会在特定时间后停止程序或截断输出。
答案 2 :(得分:8)
您可能在for
循环内的第174次迭代中调用undefined behavior,因为最大int
值可能是2147483647
但174 * 123456789
表达式求值为{{ 1}}这是未定义的行为,因为没有有符号整数溢出。因此,您正在观察UB的影响,特别是在GCC编译器中设置了优化标志。编译器可能会通过发出以下警告警告您:
2148147972
删除(warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]
)优化标记以观察不同的结果。
答案 3 :(得分:6)
编译器可以假设不会发生未定义的行为,并且因为签名溢出是UB,它可以假设从不i * 12345678 > INT_MAX
,因此也i <= INT_MAX / 12345678 < 300
,因此删除了检查i < 300
。