程序在在线IDE上表现得很奇怪

arp*_*gal 78 c++ integer-overflow undefined-behavior

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

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

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

0 0
1 12345678
2 24691356
...
297 -628300930
298 -615955252
299 -603609574
Run Code Online (Sandbox Code Playgroud)

但是,在像codechef这样的在线IDE上,它提供了以下输出:

0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597
Run Code Online (Sandbox Code Playgroud)

为什么for循环不在300处终止?此程序也始终终止4169.为什么4169而不是其他一些价值?

eer*_*ika 103

我将假设在线编译器使用GCC或兼容的编译器.当然,也允许任何其他编译器进行相同的优化,但GCC文档很好地解释了它的作用:

-faggressive-loop-optimizations

此选项告诉循环优化器使用语言约束来导出循环迭代次数的边界.这假设循环代码不会通过例如导致有符号整数溢出或超出范围的数组访问来调用未定义的行为.循环迭代次数的界限用于指导循环展开和剥离以及循环退出测试优化.默认情况下启用此选项.

该选项仅允许基于证明UB的情况做出假设.为了利用这些假设,可能需要启用其他优化,例如常量折叠.


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

为什么4169而不是其他一些价值?

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

  • @MichaelWalz:编译器无法像你建议的那样可靠地检测到UB(虽然它有时可能会发生警告,因为它实际上是_does_这里).相反,它可以在假设不存在UB_的情​​况下以最佳意图进行.这两个可能是微妙的,但实际上是完全不同的东西.它不像编译器"aha!UB!现在我可以打开_such和这样的优化_" - 优化始终存在.它正在做这样的事情_所有时间_.但是,只要您的程序编写正确,您就不会看到其可能的语义发生任何变化. (23认同)
  • 作为一个类比,你房子前门的制造商可能已经决定,如果它有一块金属战术性地放置在它的中间位置,它会更加坚固.你永远不会注意到,除非你在你的门上打洞并因此错误地使用门_. (19认同)
  • @MichaelWalz确实排出一个. (6认同)
  • 如果编译器可以证明任何"i"值大于173的UB,那为什么不发出警告而不是进行无意义的优化呢? (2认同)
  • @MichaelWalz:删除冗余布尔测试不是无意义的优化; 这是一个非常有用的.在存在破碎的源代码的情况下,添加额外的代码以禁用/撤消优化将会*(大多数)毫无意义. (2认同)

Hol*_*Cat 35

"未定义的行为是未定义的." (C)

codechef上使用的编译器似乎使用以下逻辑:

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

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

  • @ArpanMangal我只计算输出中的字符,它似乎是'2 ^ 16`.显然,两者都将输出截断为"2 ^ 16"字符是巧合. (10认同)
  • @jmoreno如果您对编译器设计感兴趣,那不是浪费时间 (6认同)
  • @jmoreno宇宙做的任何事情都是正确的(根据定义),那么研究天文学有什么意义呢? (4认同)
  • @ArpanMangal:像4169这样的鼻子恶魔,目前正在参加ideone和codechef.UB是未定义的,它可以是任何东西,包括鼻子恶魔.严肃地说,试图分析UB是浪费时间,利用这段时间来弄清楚如何防止它发生. (3认同)
  • @jmoreno虽然这次可以分析它.了解UB如何破坏东西可能有助于得出结论,在哪种情况下UB是可接受的,如果有的话. (2认同)

Ron*_*Ron 8

您正在调用未定义行为的第174次迭代可能是你的内for环路最高int值可能是2147483647尚未174 * 123456789表达式求2148147972是未定义的行为,因为没有符号的整数溢出.因此,您正在观察UB的影响,特别是在GCC编译器中设置了优化标志.编译器可能会通过发出以下警告警告您:

warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]
Run Code Online (Sandbox Code Playgroud)

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

  • @Ron:这不是自相矛盾的.程序具有明确定义的语义,或者它没有.期.请记住,C++代码不是要执行的一系列指令; 它是_a program_的描述. (11认同)
  • 值得注意的是,未定义的行为可能具有*retroactive*效果 - 因为UB将在迭代174上发生,标准甚至不要求循环的前173次迭代应按预期进行! (3认同)

yas*_*sin 6

编译器可以假设不会发生未定义的行为,并且因为签名溢出是UB,它可以假设从不i * 12345678 > INT_MAX,因此也i <= INT_MAX / 12345678 < 300因此删除了检查i < 300.