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

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

int i = 0;
while(i < 100)
{
int var = 4;
i++;
}

声明 int var一百次。我觉得应该有,但我不确定。这样做是否更实际/更快捷:

int i = 0;
int var;
while(i < 100)
{
var = 4;
i++;
}

还是在速度和效率方面都一样?

54642 次浏览

大多数现代编译器都会为您优化这一点。话虽如此,我还是会用你的第一个例子,因为我觉得它更具可读性。

现在最好在循环中声明它,除非它是一个常量,因为编译器将能够更好地优化代码(减少变量作用域)。

编辑: 这个答案现在基本上已经过时了。随着后经典编译器的兴起,编译器无法识别的情况越来越少。我仍然可以构造它们,但是大多数人会把它们归类为不好的代码。

唯一能确定的方法就是给他们计时。但是,如果有的话,这种差异将是微小的,所以你将需要一个强大的时间循环。

更重要的是,第一个样式更好,因为它初始化了变量 var,而另一个样式没有初始化它。这一点以及应该尽可能接近变量的使用点来定义变量的指导原则意味着通常应该首选第一种形式。

局部变量的堆栈空间通常在函数范围内分配。所以在循环中不会发生堆栈指针调整,只需要将4赋给 var。因此,这两个代码片段具有相同的开销。

对于基元类型和 POD 类型,它没有区别。编译器将在函数的开头为变量分配堆栈空间,并在两种情况下函数都返回时释放它。

对于具有非平凡构造函数的非 POD 类型来说,这将产生不同的结果——在这种情况下,将变量置于循环之外只会调用构造函数和析构函数一次,并且每次迭代都会调用赋值操作符,而将其置于循环内则会在循环的每次迭代中调用构造函数和析构函数。取决于类的构造函数、析构函数和赋值操作符所做的事情,这可能是可取的,也可能不是可取的。

那不是真的 有开销,但是它忽略了能够开销。

即使它们可能最终会在栈上的同一个位置,它仍然分配它。 它将在堆栈上为 int 分配内存位置,然后在}结束时释放它。不是在堆自由意义上,它将移动 sp (堆栈指针)1。 在您的例子中,考虑到它只有一个局部变量,它只是简单地等同于 fp (frame 指针)和 sp

简短的回答是: 不要在意任何一种方式的工作几乎是一样的。

但是试着多读一些关于栈是如何组织的,我的本科学校有很多关于这方面的讲座 如果你想看更多,请看这里 Http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/assembler1/lecture.html

它们都是一样的,下面是通过查看编译器执行的操作(即使没有将优化设置为高级)可以找到答案的方法:

看看编译器(gcc 4.0)对简单示例做了什么:

1. c:

main(){ int var; while(int i < 100) { var = 4; } }

Gcc-S1. c

第1条:

_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

2. c

main() { while(int i < 100) { int var = 4; } }

Gcc-S2. 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

通过这些,您可以看到两件事情: 首先,两者的代码是相同的。

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

         subl    $24, %esp

最后,循环中唯一的事情就是赋值和条件检查:

L3:
movl    $4, -12(%ebp)
L2:
cmpl    $99, -16(%ebp)
jle     L3

在不完全删除循环的情况下,这已经是最高效的了。

两个循环具有相同的效率。它们都需要无限长的时间:)在循环中增加 i 可能是个好主意。

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

但是,如果变量是一个具有非平凡构造函数/析构函数的类,那么运行时成本很可能会有很大的差异。我通常将变量的作用域设置为循环内部(以使作用域尽可能小) ,但是如果结果是有 perf 影响的话,我会考虑将类变量移出循环的作用域。然而,这样做需要一些额外的分析,因为 ode 路径的语义可能会改变,所以只有在语义允许的情况下才能做到这一点。

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

与下列情况完全不同:

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


// do stuff...


}

如果只有两个变量,编译器可能会为这两个变量分配一个寄存器。反正这些收银机都在那儿,所以不用花时间。两种情况下都有2个寄存器写和1个寄存器读指令。

我认为大多数的答案都忽略了一个重要的问题,那就是: “它清楚吗?”很明显,通过所有的讨论,事实是; 不,它不清楚。 我建议在大多数循环代码中,效率几乎不是问题(除非你计算火星着陆器) ,所以真正的问题是什么看起来更合理、更可读、更易维护——在这种情况下,我建议在循环之前和之外声明变量——这只是让它更清晰。那么像你和我这样的人甚至不会浪费时间在网上查看它是否有效。

我曾经运行过一些性能测试,令我惊讶的是,发现 case 1实际上更快!我认为这可能是因为在循环中声明变量减少了它的作用域,所以它被提前释放了。然而,那是很久以前的事了,在一个非常古老的编译器上。我确信现代编译器在优化消除差异方面做得更好,但是尽可能缩短变量作用域也没什么坏处。

#include <stdio.h>
int main()
{
for(int i = 0; i < 10; i++)
{
int test;
if(i == 0)
test = 100;
printf("%d\n", test);
}
}

上面的代码总是打印100次,这意味着每次函数调用只分配一次循环内的局部变量。