我应该如何摆脱循环? [关闭]
Posted
技术标签:
【中文标题】我应该如何摆脱循环? [关闭]【英文标题】:How should I break out from a loop? [closed] 【发布时间】:2016-05-05 14:34:25 【问题描述】:每当我需要从 C++ 中的for(unsigned int i=0;i<bound;++i)
表达式中跳出时,我只需设置索引变量i=bound
,与此answer 中描述的方法相同。我倾向于避免使用break
声明,因为老实说,我不太了解它实际上 的作用。
比较两条指令:
for(unsigned int i=0;i<bound;++i)
if (I need a break)
break;
和
for(unsigned int i=0;i<bound;++i)
if (I need a break)
i=bound;
我推测第二种方法在i
和bound
之间进行了一个额外的变量集,然后进行了一次额外的比较,因此从性能的角度来看,它看起来更昂贵。那么问题是打电话给break
,然后做这两个测试更便宜吗?编译的二进制文件有什么不同吗?是否存在第二种方法中断的情况,或者我可以安全地选择这两种方法中的任何一种吗?
相关:Does `break` work only for `for`, `while`, `do-while`, `switch' and for `if` statements?
Breaking out of a loop without a break statement [C]
【问题讨论】:
"break" 表示执行会跳转到最内层循环之后的第一条语句 为了回答您的性能问题,比较两个版本之间生成的程序集(启用优化) 第二种方法似乎很危险,因为有一天循环限制变量将从bound
更改为size
,而您会忘记替换第二个bound
。
“我不太了解它实际上做了什么。”好吧,这很奇怪,它是最简单的构造之一。它只是立即跳出循环。
1) 使用更清楚地表达意图的结构(并了解break
语句的行为 - 大多数程序员可能会更清楚); 2) 可能没有任何后果的性能差异; 3)可能存在行为差异(特别是如果循环控制变量不是循环的本地变量),请注意这一点; 4)正如其他人所指出的,可能会对未来的代码维护产生影响
【参考方案1】:
使用 break 将更具前瞻性和逻辑性。
考虑以下示例,
for (i = 0; i < NUM_OF_ELEMENTS; i++)
if(data[i] == expected_item)
break;
printf("\n Element %d is at index %d\n", expected_item, i);
但是第二种方法在这里用处不大。
【讨论】:
此时考虑性能是过早的优化。你应该做任何更清楚的事情,那就是使用break
。
有见地。我想这又是一种个人偏好和编码风格,我不会在循环范围之外移动索引变量。
与this comment on the other answer 中提出的问题相同。
@Matsmath:通常您将索引变量保留在for
循环范围内,仅当您在循环外确实需要它的值时“将其拉出”。
@MatteoItalia:这取决于您以后是否需要索引。如果没有,您应该在循环本身中声明它以保证严格的局部性。【参考方案2】:
我想到了三个主要的技术差异:
正如其他人所说,如果您的索引变量不限于for
范围break
保持不变,而您的方法会破坏其内容;当您搜索时带有break
的数组,代码更简洁(您不必保留额外的变量来记下您停止的位置);
break
立即退出循环;您的方法要求您执行身体的其余部分。当然你总是可以写:
for(int i=0; i<n; ++i)
if(...)
i=n;
else
rest of the loop body
但它会给你的循环增加视觉和逻辑上的混乱;
break
几乎肯定会被转换为简单的jmp
到循环之后的指令(尽管,如果你有带析构函数的块范围变量,情况可能会更复杂);您的解决方案不一定被编译器识别为等效的。
您实际上可以看到here,gcc 一路生成将n
移动到i
的代码,而在第二种情况下,它直接跳出循环。
在风格方面:
我发现“你的方式”过于复杂而且不习惯 - 如果我在实际代码中遇到它,我会问自己“他为什么不直接使用break
?”,然后检查两次以确保这并不是说我错过了一些副作用,并且意图实际上只是为了跳出循环;
正如其他人所说,您的内部分配存在与实际循环条件不同步的风险;
当循环条件变得比简单的范围检查更复杂时,无论是在逻辑方面(如果循环条件复杂,那么欺骗它会变得更复杂)和性能方面(如果循环条件很昂贵,您已经知道要退出您不想再次检查它);这也可以通过添加一个额外的变量来规避(这通常在缺少 break
的语言中完成),但这又是对算法实际工作的额外干扰;
它根本不适用于基于范围的循环。
【讨论】:
非常感谢这个程序集输出。这无疑将我推向了正确的方向。 一个更好的例子是两个具有相同行为的函数,因此如果编译器“看穿”了这个奇怪的习语,它们可以编译为相同的 asm。我moved theret += arr[i]
above the if
in your example。事实证明,gcc 6.1 使用cmov
作为if
,这将循环计数器依赖链的长度从 1 个周期增加到 3 个周期(Intel pre-Broadwell)或 2 个周期(Broadwell 和更高版本)。在没有展开的情况下,“正常”版本可以每 2 个周期运行一个,但 i=c
版本只能每 3 个周期运行一个(Broadwell 之前)。
@PeterCordes:哎呀,这绝对不是故意的;我会尽快修复链接。
完美的例子说明了为什么i=c
成语是可怕的!已经在讨论它的答案中导致了琐碎代码中的错误。当你更新你的链接时,你应该prob。省略-ansi
。如果您指定-std=c++11
,那没有任何意义。 (我使用了-xc -std=c11
,因为 Godbolt 不直接提供 C 编译器。)也不需要:-m32
。 64 位 ABI 在 regs 中传递 args,因此噪音更小。【参考方案3】:
我更喜欢break;
,因为它使循环变量保持不变。
我在搜索内容时经常使用此表单:
int i;
for(i=0; i<list.size(); ++i)
if (list[i] == target) // I found what I'm looking for!
break; // Stop searching by ending the loop.
if (i == list.size() ) // I still haven't found what I'm looking for -U2
// Not found.
else
// Do work with list[i].
编译后的二进制文件有什么不同吗?
几乎可以肯定。(尽管优化器可能会识别您的模式,并将它们减少到几乎相同)break;
语句可能是一个汇编“跳转”语句,用于跳转到列表之外的下一条指令,同时保持控制变量不变。
分配变量(在非优化代码中)将导致分配给控制变量,测试该变量,然后跳转到结束循环。
正如其他人所提到的,将变量分配给它的最终值不太适合未来,以防你的循环条件在未来发生变化。
一般来说,当您说:“我不太了解它的实际作用。(所以我使用了一种解决方法)”, 我的回答是:“花时间了解它的作用!作为程序员,你工作的一个主要方面是学习东西。”
【讨论】:
您确定可以在循环外访问i
吗? (我删除了我写了一半的答案:-))
@SouravGhosh 在上面的代码中,他不能。但是你可以在循环之前定义 i 。诠释 i = 0; for(i = 0...
@abelenky 是的,但是如果我们遵循 OP 的代码,那么这个答案的意义何在? (不过,总的来说,你是对的)
感谢您的回答。虽然,从您的帖子中,从性能的角度来看,无条件指令跳转是便宜的还是昂贵的,这一点根本不清楚。这就是我想学习的东西。
你认为break;
会作为关键字包含在语言中,如果它具有巨大的性能成本吗?每条汇编指令的成本都应该被认为是非常便宜的。昂贵的代码来自以非常糟糕的方式将指令组合在一起(例如,冒泡排序、大量内存副本、排序数据的线性搜索等)成本并非来自单个指令。【参考方案4】:
使用break
这样做是惯用的,应该是默认设置,除非出于某种原因,相当模糊的替代方案可以为下面的逻辑奠定基础。即便如此,我还是更愿意在循环退出后进行变量设置,为了清晰起见,将该设置移近它的用法。
我无法想象这样的场景,其中的性能足以让我担心。也许一个更复杂的例子可以证明这一点。如前所述,答案几乎总是“测量,然后调整”。
【讨论】:
【参考方案5】:除了使用break
语句退出for
或[do
] while
循环之外,还允许使用goto
来打破嵌套循环,例如:
for (i=0; i<k; i++)
for (j=0; j<l; j++)
if (someCondition)
goto end_i;
end_i:
【讨论】:
确实,有一次我遇到了一些非常讨厌的算法,除了依靠古老的goto
调用之外,没有其他(简单)方法可以做到这一点。这启发了我this related question。
@Matsmath,对于您参考中的状态机,我将编写一个 C 程序来解析状态定义并在 C 中发出一个状态机。该 C 代码将充满 goto
语句不会'没关系,因为它是机器生成的(但你必须证明生成器是正确的)。
perl 在此之上添加了语法糖:you label the outer loop and use last LABEL
。但无论如何,@Matsmath:如果你理解了goto
的概念,那么break
完全等同于goto
到循环外的标签。以上是关于我应该如何摆脱循环? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章