与 C 相比,C++ 中无副作用的无限循环的好处是未定义行为?

问题描述 投票:0回答:2

在 C++ 中循环为

for(;;) {}

是未定义的行为,但它们不在 C 中?

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2809r0.html 中表示这样做有“充分的理由”。有没有简单的例子可以清楚地说明这一点?

c++ c loops optimization undefined-behavior
2个回答
34
投票

如果编译器可以假设所有没有副作用的循环都终止,则不必证明这一点。

如果允许非终止循环,则只有在能够证明终止的情况下才允许编译器执行某些优化,这通常是不可能的,因此它会变成模式识别游戏。为了什么好处?

根本问题是非终止本身是一种副作用。

当且仅当循环终止时,才会观察到循环终止后肯定会发生的任何可观察到的效果,即使循环没有效果. 当然可以对

if(expr) return;

进行完全相同的推理,编译器不允许将

if
之后的内容移动到
if
之前,除非它可以证明
expr
是假的。但是
if
是基本的控制流机制,而非终止循环则不是(恕我直言)。
获取以下代码。

int collatz_conjecture(int i){ while(i!=1){ if ((i%2)==0) i/=2; else i=i*3+1; } return i; } int main(){ collatz_conjecture(10); return 5; }

使用 O3,gcc 将其编译为:

collatz_conjecture(int): mov eax, 1 ret main: mov eax, 5 ret

那么,编译器是否证明了 Collatz 猜想,以确定它应该对所有数字返回 
1

?当然不是,这只是终止假设允许的优化之一(并且 UB 可能发生)。循环终止的唯一方法是 if

i==1
,因此它可以在循环后假定
i==1
并将其用于进一步优化 -> 函数始终返回 1,因此可以减少到它。
更有用的例子可以是交错复制。如果你有

loop A loop B

即使不知道 
A

终止,编译器也可以交错它们。许多矢量化操作都依赖于这个假设。

类似地,在循环之前重新排序一些独立的循环后操作会假定循环将终止。

编辑:我的意思是科拉茨猜想,而不是哥德巴赫......


1
投票

大多数有用的优化都是通过指定如果循环中没有单独的操作相对于后面的代码片段进行排序来实现的,则循环的执行作为一个整体也不需要被视为已排序。对于无限循环“之后”出现的代码来说,这有点“手动”,但它清楚地表明了它将允许编译器做什么。除其他事项外,

如果在程序终止之前不会对循环内的单个操作进行排序,则可以完全省略整个循环的执行

这种规则所体现的一个重要原则是,在循环内的代码和循环外的代码之间引入依赖关系的变换也会引入排序关系,而当前的规则中缺少该原则。如果某个条件为真时循环将退出,并且循环后的代码检查该条件,则编译器可以使用先前检查的结果来避免重复测试,但这意味着循环后的代码依赖于根据循环内计算的值。

请注意,如果在某些情况下需要使用外部手段终止应用程序,那么在保持数据依赖性的同时给予编译器重新排序代码的自由并不会排除正确的应用程序在输入一些可能的输入时可能陷入无限循环的可能性可以接受。然而,允许它们重新排序代码而不考虑结果数据依赖性,将会产生具有讽刺意义的效果,即加速主要是“错误”的程序,这些程序不能依赖这些程序来满足应用程序需求,并且对大多数添加额外检查的程序没有任何好处(否则不需要满足应用程序要求)以防止无限循环。

© www.soinside.com 2019 - 2024. All rights reserved.