为什么这个循环是无限的?

Why is this loop being endless?

我已经编程很长时间了,偶然发现了一些非常奇怪的东西。

在我的代码中某处有以下代码:

for(int i = 0; i < 512; i++){
    if((i * 12391823) % 5 == 1){
        std::cout << i << std::endl;
    }
}

我已经将问题追踪到这段代码。 我正在使用 CLion。编译 Debug 版本时,循环不是无限的,最终会在打印几个数字后结束。

然而,当构建为 Release 时,它​​似乎永远不会退出循环。

...
>>> 15968768
>>> 15968773
>>> 15968778
...

如果 (i * 12391823) 被替换为不同的(较小的)数字,则不会发生这种情况。

例如使用 (i * 123),它确实很好地退出:

...
>>> 482
>>> 487
>>> 492
...

我还查看了显示以下内容的编译输出:

warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]
   16 |         if((i * 12391823) % 5 == 1){
      |            ~~~^~~~~~~~~~~

我确实想知道为什么这会导致循环没有结束。 它似乎溢出了,但它不应该改变 i 并因此在某个时候结束循环,对吧?

很高兴得到关于这个话题的解释!

问候 芬恩

您遇到了以下行中的未定义行为:

if((i * 12391823) % 5 == 1){

这是因为对于大于173的i,乘法结果超出了整数范围。

当涉及到未定义的行为时,您可以自由使用编译器。在优化的构建中,他们倾向于编译掉导致它的表达式中和周围的一些(甚至是大的)代码块。正如您刚刚经历的那样 - 它甚至可能传播到本身正确的代码(for 循环中的退出条件)。

BTW 整数溢出(据我所知)仅对有符号整数导致未定义的行为,它对无符号类型有很好的定义(结果被截断)。您可能想尝试使用无符号类型的 i(但这仍然可能会产生您没有预料到的结果)。

如评论中所述,循环中生成的值,特别是此子表达式:

(i * 12391823)

i 的较大值会溢出。这会导致未定义的行为。

问题已通过为 i 使用更宽的类型解决,例如 long long


注意:如果您使用无符号类型,如果超过最大限制,它们将环绕而不是溢出。

I do wonder why this would lead to the loop not ending. It seems to overflow, yet it is not supposed to change i and therefor end the loop at some point, right?

答案是“未定义的行为是未定义的”。它无所不能。但是,您从 aggressive-loop-optimizations 收到警告这一事实可能暗示了循环变得无穷无尽的原因。编译器可能决定将您的循环修改为

for(int i = 0; i < 512 * 12391823; i + 12391823){
    if(i % 5 == 1){
        std::cout << i << std::endl;
    }
}

甚至可能

for(int i = 24783646; i < 512 * 12391823; i + 12391823 * 5){
    std::cout << i << std::endl;
}

当整数溢出发生时,这两个选项的行为可能会很奇怪。


解决方案是不要进入未定义行为区域。例如,您可以通过将 i 的类型从 int 更改为 unsigned long long

for(unsigned long long i = 0; i < 512; i++)