这个问题在这里已经有答案了:

我只是想知道如果你这样做,是否会造成速度或效率的损失:

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

其中声明 int var 一百次。在我看来好像会有,但我不确定。这样做会更实用/更快吗:

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

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

有帮助吗?

解决方案

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

其他提示

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

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

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

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

1.c:

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

海湾合作委员会-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

2.c

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

海湾合作委员会-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

从这些,你可以看出两点:首先,两者的代码是相同的。

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

         subl    $24, %esp

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

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

在不完全删除循环的情况下,这大约是最有效的。

如今,最好在循环内声明它,除非它是常量,因为编译器将能够更好地优化代码(减少变量范围)。

编辑:这个答案现在基本上已经过时了。随着后经典编译器的兴起,编译器无法解决的情况越来越少。我仍然可以构建它们,但大多数人会将这种构建归类为糟糕的代码。

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

对于内置类型,两种样式之间可能没有区别(可能就生成的代码而言)。

但是,如果变量是具有重要构造函数/析构函数的类,则运行时成本很可能存在重大差异。我通常会将变量的范围限制在循环内部(以保持范围尽可能小),但如果这对性能产生影响,我会考虑将类变量移到循环范围之外。然而,这样做需要一些额外的分析,因为颂歌路径的语义可能会改变,所以只有在语义允许的情况下才能这样做。

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

}

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

我曾经进行过一些性能测试,令我惊讶的是,情况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 10 次,这意味着每次函数调用循环内的局部变量只分配一次。

唯一确定的方法就是给它们计时。但是,即使存在差异,差异也将是微小的,因此您将需要一个强大的定时循环。

更重要的是,第一个是更好的风格,因为它初始化变量 var,而另一个则使其未初始化。这以及定义变量应尽可能接近其使用点的准则意味着通常应首选第一种形式。

如果只有两个变量,编译器可能会为这两个变量分配一个寄存器。无论如何,这些寄存器都在那里,所以这并不需要时间。无论哪种情况,都有 2 个寄存器写入指令和 1 个寄存器读取指令。

我认为大多数答案都忽略了一个需要考虑的要点:“清楚了吗?”显然,通过所有讨论,事实是这样的;不它不是。我建议在大多数循环代码中,效率几乎不是问题(除非您计算火星着陆器),所以实际上唯一的问题是什么看起来更明智、可读和可维护 - 在这种情况下,我建议声明前面和循环外部的变量 - 这只是让它更清晰。那么像你我这样的人甚至不会费心去浪费时间在线检查它是否有效。

那不是真的,但是它的开销却是忽视的。

即使它们可能最终位于堆栈上的同一位置,它仍然会分配它。它将在堆栈上为该 int 分配内存位置,然后在 } 末尾释放它。不是在堆自由意义上,它会将 sp(堆栈指针)移动 1。在你的情况下,考虑到它只有一个局部变量,它只会简单地将 fp(帧指针) 和 sp 等同起来

简短的回答是:不关心任何一种方式几乎都是一样的。

但请尝试阅读更多有关堆栈如何组织的内容。如果您想在这里阅读更多检查,我的本科学校就有很好的讲座http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top