在循环中声明变量是否有任何开销?(C++)

151 c++ loops variable-declaration

我只是想知道如果你做了这样的事情会有任何速度或效率的损失:

int i = 0;
while(i < 100)
{
    int var = 4;
    i++;
}
Run Code Online (Sandbox Code Playgroud)

宣告int var一百次.在我看来会有,但我不确定.这样做会更实际/更快:

int i = 0;
int var;
while(i < 100)
{
    var = 4;
    i++;
}
Run Code Online (Sandbox Code Playgroud)

或者它们是相同的,速度和效率方面的?

laa*_*lto 190

局部变量的堆栈空间通常在函数范围内分配.因此,循环内部不会发生堆栈指针调整,只需指定4即可var.因此,这两个片段具有相同的开销.

  • 我希望那些在大学教书的人至少知道这个基本的东西.有一次他嘲笑我在一个循环中声明一个变量,我想知道什么是错的,直到他把性能作为不这样做的理由而且我就像"WTF!?". (50认同)
  • 你确定你应该马上谈论堆栈空间.像这样的变量也可以在寄存器中. (18认同)
  • @toto像这样的变量也可以_nowhere_ - 在`var`变量初始化,但从来没有使用过,所以合理的优化器可以只将其彻底删除(除非****如果该变量是在循环之后的某个地方使用的第二个片段) . (3认同)

Ada*_*eld 98

对于原始类型和POD类型,它没有任何区别.编译器将在函数开头为变量分配堆栈空间,并在函数返回时解除分配.

对于具有非平凡构造函数的非POD类类型,它会产生影响 - 在这种情况下,将变量放在循环之外只会调用构造函数和析构函数一次,并且每次迭代都会调用赋值运算符,而将其置于loop将为循环的每次迭代调用构造函数和析构函数.根据类的构造函数,析构函数和赋值运算符的作用,这可能是也可能不是.

  • 正确的想法错误的原因.循环外变量.构造一次,销毁一次,但是对齐运算符应用每次迭代.循环中的变量.Constructe/Desatructor适用于每次迭代但零赋值操作. (39认同)
  • 这是最好的答案,但这些评论令人困惑.调用构造函数和赋值运算符之间有很大的区别. (7认同)
  • @Andrew Grant:为什么.赋值运算符通常被定义为复制构造到tmp,然后是swap(异常安全),然后是destroy tmp.因此,赋值运算符与上面的构造/销毁循环没有什么不同.有关典型赋值运算符的示例,请参见http://stackoverflow.com/questions/255612/c-dynamically-allocating-an-array-of-objects/255744#255744. (7认同)

Ale*_*own 67

它们都是相同的,通过查看编译器的功能(即使没有将优化设置为高),您可以通过以下方式找到它们:

看看编译器(gcc 4.0)对你的简单例子的作用:

1.C:

main(){ int var; while(int i < 100) { var = 4; } }
Run Code Online (Sandbox Code Playgroud)

gcc -S 1.c.

1.S:

_main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $0, -16(%ebp)
    jmp L2
L3:
    movl    $4, -12(%ebp)
L2:
    cmpl    $99, -16(%ebp)
    jle L3
    leave
    ret
Run Code Online (Sandbox Code Playgroud)

2.C

main() { while(int i < 100) { int var = 4; } }
Run Code Online (Sandbox Code Playgroud)

gcc -S 2.c.

2.S:

_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    $0, -16(%ebp)
        jmp     L2
L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3
        leave
        ret
Run Code Online (Sandbox Code Playgroud)

从这些中,您可以看到两件事:首先,两者中的代码是相同的.

其次,var的存储在循环外部分配:

         subl    $24, %esp
Run Code Online (Sandbox Code Playgroud)

最后,循环中唯一的东西是赋值和条件检查:

L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3
Run Code Online (Sandbox Code Playgroud)

如果不完全删除循环,那么效率就会高得多.

  • "如果没有完全移除循环,那么效率就会高得多"不完全.部分展开循环(每次传递4次)会大大加快它的速度.可能有许多其他方法可以优化......尽管大多数现代编译器可能会意识到循环没有任何意义.如果以后使用'i',它只需设置'i'= 100. (2认同)
  • 我喜欢用证据支持理论的答案!很高兴看到 ASM 转储支持相等代码的理论。+1 (2认同)

Jos*_*hua 12

这些天最好在循环中声明它,除非它是一个常量,因为编译器将能够更好地优化代码(减少变量范围).

编辑:这个答案现在已经过时了.随着后经典编译器的兴起,编译器无法弄清楚的情况越来越少.我仍然可以构建它们,但大多数人会将构造归类为坏代码.

  • 我怀疑它会影响优化 - 如果编译器执行任何类型的数据流分析,它可以发现它没有在循环外被修改,所以它应该在两种情况下产生相同的优化代码. (4认同)
  • 如果您使用相同的临时变量名称有两个不同的循环,它将无法弄明白. (3认同)

And*_*are 10

大多数现代编译器都会为您优化.话虽如此,我会使用你的第一个例子,因为我发现它更具可读性.

  • 我并不认为它是一种优化.由于它们是局部变量,因此只在函数的开头分配堆栈空间.没有真正的"创造"涉及损害性能(除非正在调用构造函数,这完全是另一个故事). (3认同)

Mic*_*urr 8

对于内置类型,两种样式之间可能没有区别(可能直到生成的代码).

但是,如果变量是具有非平凡构造函数/析构函数的类,那么运行时成本可能会有很大差异.我通常将变量范围限定在循环内部(以保持范围尽可能小),但如果事实证明具有性能影响,我会将类变量移到循环范围之外.但是,这样做需要一些额外的分析,因为ode路径的语义可能会发生变化,因此只有在sematics允许的情况下才能这样做.

RAII类可能需要此行为.例如,可能需要在每次循环迭代时创建和销毁管理文件访问生存期的类,以正确管理文件访问.

假设你有一个LockMgr类在构造时获取一个关键部分,并在销毁时释放它:

while (i< 100) {
    LockMgr lock( myCriticalSection); // acquires a critical section at start of
                                      //    each loop iteration

    // do stuff...

}   // critical section is released at end of each loop iteration
Run Code Online (Sandbox Code Playgroud)

与以下内容完全不同:

LockMgr lock( myCriticalSection);
while (i< 100) {

    // do stuff...

}
Run Code Online (Sandbox Code Playgroud)


Lar*_*abe 6

两个循环都具有相同的效率.他们都会花费无限的时间:)在循环中增加i可能是个好主意.