为什么这个循环是无限的?
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++)
我已经编程很长时间了,偶然发现了一些非常奇怪的东西。
在我的代码中某处有以下代码:
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++)