在循环体内声明变量有什么缺点吗?

3

假设我们有一个循环,需要迭代多次:

for (int i=0; i < 1000000; ++i) {
  int s = 100;
  s += i;
  cout << s;
}

我们只在循环体内使用s,因此理想情况下,我们希望在那里声明它,这样它就不会污染封闭的命名空间。

我想知道是否有任何缺点。例如,它会产生性能成本吗,因为程序在每次迭代时重新声明s


2
是的,在每次迭代中它都会被构建和销毁。 - deW1
1
@deW1 我很想听听关于编译器如何针对简单类型进行优化的权威答案。 - jwimberley
1
顺便问一下,有什么其他的选择吗?只需将整个循环放在块中即可? - Dun Peal
1
我相信这里已经有答案了:https://dev59.com/jXNA5IYBdhLWcg3wYMx-(有点C++特定)和这里:https://dev59.com/W3RC5IYBdhLWcg3wG9Rb(有点Java特定)。总的来说,编译器很可能会优化这个过程,并且性能差异可以忽略不计。 - jwimberley
2
  1. 在外部声明它
  2. int i 之后声明它,两者都可以工作
- deW1
显示剩余2条评论
3个回答

7
< p >概念上,那个变量在每次迭代时都会被构造和销毁。< /p > < p >但是它会影响性能吗?好的,您可以在此处检查您的情况。删除第7行的int以在循环本地和函数本地变量之间进行切换。
结论:没有任何区别。汇编代码相同!< /p > < p >因此,在您的代码中使用有意义的内容。如果您需要每次迭代一个对象,请创建一个对象。优化器比您想象的聪明。如果这还不够,您将使用分析数据和仔细调整回来,而不是广泛的指导方针。< /p >

太棒了。因此,在概念上,它是不同的,在理论上,这可能会对一个天真的编译器造成性能损失,但在实践中,现代编译器应该能够优化掉这个问题。 - Dun Peal
1
@DunPeal 没错。即使我已经看过很多C++代码,编译器所做的优化仍然让我惊叹不已。尽可能使用const关键字,并提高优化级别,你将会看到奇迹般的效果。 - Quentin

2
是的。在循环内部声明变量会导致它在每次迭代时被拆解和重建。对于小循环和简单数据类型,编译器会进行优化,这可能不太明显,但是当涉及到复杂对象和大型循环时,最好在外部声明变量。
如果循环中的变量占用过多内存,可以将循环和声明括在大括号中,使大括号内分配的所有变量在退出后被删除。大多数情况下,这样的微观优化并不重要,但如果您使用复杂的类等,请在外部初始化变量并在每次重置它时使用。
通常不建议声明过多变量,这会使您的代码难以阅读并增加内存使用量。如果可以的话,不需要声明变量时就不要声明。例如,您的示例可以简化为for(int i = 0;i<1000000;i++)cout<<i+100;。如果这样的优化可行且不会使您的代码难以阅读,请使用它们。

1
"你的示例可以简化为 for(int i = 100;y<1000100;y++){cout<<i}。值得注意的是,一个好的编译器很可能会发现这一点,并优化代码,就像代码是这样编写的一样。我的建议通常是首先编写易于阅读的代码,只有在分析找到瓶颈后才进行优化。" - cdhowie
@cdhowie 我说编译器通常会优化这些循环,只有当你的循环非常复杂时才需要担心这个问题。 - user6244076
2
如果变量使您的代码变得更 阅读,那么您可能在误用/错命名它们。例如,在这里将 +100 折叠到循环标题中会混淆迭代次数和数字偏移量。我认为这实际上不够清晰。 - Quentin
@Quentin 你是对的。我修改了答案。 - user6244076
在我的看法中,在循环头初始化一个在循环条件中没有使用的变量仍然看起来很混乱。for(int i = 0;y<1000000;y++) - zett42
@zett42 抱歉,那是个打错字。已经修正了。 - user6244076

1

销毁一个 int 是一个空操作。变量停止存在,但不需要运行任何运行时代码。

引用或指向停止存在的变量的指针具有未定义行为。在初始化之前,新创建的局部变量具有未定义状态。因此,仅仅为一个新变量重复使用旧变量是合法的,编译器不必证明没有这样的未完成引用。

在这种情况下,如果它能够证明值是恒定的 100,它甚至可以跳过除第一次初始化外的所有操作。并且它可以“提前”进行此初始化,因为没有定义的方法来检测其提前发生。在这种情况下,它很容易,大多数编译器都会轻松处理它;然而,在更复杂的情况下,则较少如此。如果您将其标记为const,则编译器不再需要证明它未被修改,而是可以假设它!

C++ 中许多未定义的区域存在,是为了使某些优化易于实现。

现在,如果你有更复杂的东西,比如一个vector<int>{1,2,3,4,5},销毁和创建就不再是无操作了。虽然仍然可以将变量“提升”出循环,但对于编译器来说会更加困难。这是因为动态分配有时很难进行优化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接