2013-05-26 64 views
3

如果我这样做的一个(我假设是等价的这个问题的目的)在循环中声明变量是否有效?

for(int i=0; i<A; i++) 
{ 
    //... do stuff 
    for(int j=0; j<B; j++) 
    { 
    //... do stuff 
    } 
    //... do stuff 
} 

for(int i=0; i<A; i++) 
{ 
    int j; 
    //... do stuff 
} 

是否可变j堆栈上的每个循环得到重建(是SP不断更新的每一个回路) ,还是编译器足够聪明,知道一次函数可能有多少个局部变量,然后在函数入口处为堆栈中的所有变量腾出空间?

据我所知,这在理论上依赖于编译器,但我认为所有主要编译器都是这样的。如果没有,是否有人具体了解GCCVC++编译器?

+4

您可以查看使用不同优化级别生成的程序集。这很可能只会创建一次变量。 – juanchopanza

+0

通常会被优化掉。不要打扰在外面宣布它。这不是必需的。 – dchhetri

回答

2

我相信变量只会被创建一次,虽然我不在乎,我也不相信你也应该。

这可能是您预先优化(或不必要的优化)的一个例子;通过在循环中声明变量创建的潜在低效率非常小,并且通过在不同位置声明变量来“优化”代码将对程序的整体运行时和内存使用率产生微不足道的影响。

考虑花时间优化您的算法并找到高效的数据结构,因为这可能会更好地利用您的时间。

+0

从某种意义上说,这是一个关于编译器的问题,而不是代码优化。我实际上并不关心这一点,只是好奇而已。 – baruch

0

编译器今天太强大了,无法优化这些东西。所以不要打扰,并根据您的方便使用。不成熟的优化更加邪恶。

0

为什么不自己尝试一下,看看?

class foo { 
public: 
    foo() { std::cout << "Construct\n"; } 
    ~foo() { std::cout << "Destruct\n"; } 
    }; 

int main() { 
    for (int i = 0; i < 10; ++i) { 
     foo f; 
    } 

    return 0; 
} 

你会看到,f构造(和析构函数!)获得通过每一次循环中调用。所以你的问题的答案是“是的,变量每次都通过循环重新创建。”回到你的例子,你声明了一个int,它有一个构造函数和析构函数,它什么都不做。所以,在你的循环中声明一个int没有任何性能损失。

+3

构造函数和析构函数是堆栈调整的一个单独问题。 – aschepler

+2

这与内置类型不同,因为内置函数没有隐式的构造函数/析构函数或初始化。 –

+0

我在问堆叠调整。可以通过放置'new'来调用构造函数,而不必通过放置'new'来调用构造函数,这对每个对象的构造都会发生什么(分配内存然后调用构造函数调用内存) – baruch

5

它是有效的。像大多数现代编译器一样,gcc将会优化它。同时请记住什么高德纳说:

我们应该忘记小的效率,说约97%的时间: 过早的优化是所有罪恶的根源。

您可以通过比较汇编代码来检查它是否有效。使用例如diff来做比较。要生成装配使用-S标志,gcc -S。这相当于gcc -S -O0,因为默认优化级别为0.因此,即使在最低级别的优化级别上,gcc也会为您处理此变量。

第一个版本(为方便阅读,更喜欢这种方式):

#include <stdio.h> 

int main() 
{ 
    for (int i = 0; i < 10; i++) 
    { 
     for (int j = 0; j < 10; j++) 
     { 
      printf("%d ", i + j); 
     } 
    } 
    return 0; 
} 

第二个版本:

#include <stdio.h> 

int main() 
{ 
    for (int i = 0; i < 10; i++) 
    { 
     int j; 
     for (j = 0; j < 10; j++) 
     { 
      printf("%d ", i + j); 
     } 
    } 
    return 0; 
} 

相同的组装结果:

.file "main.cpp" 
    .section .rodata 
.LC0: 
    .string "%d " 
    .text 
.globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    .cfi_personality 0x3,__gxx_personality_v0 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    movq %rsp, %rbp 
    .cfi_offset 6, -16 
    .cfi_def_cfa_register 6 
    subq $16, %rsp 
    movl $0, -4(%rbp) 
    jmp .L2 
.L5: 
    movl $0, -8(%rbp) 
    jmp .L3 
.L4: 
    movl -8(%rbp), %eax 
    movl -4(%rbp), %edx 
    leal (%rdx,%rax), %eax 
    movl %eax, %esi 
    movl $.LC0, %edi 
    movl $0, %eax 
    call printf 
    addl $1, -8(%rbp) 
.L3: 
    cmpl $9, -8(%rbp) 
    setle %al 
    testb %al, %al 
    jne .L4 
    addl $1, -4(%rbp) 
.L2: 
    cmpl $9, -4(%rbp) 
    setle %al 
    testb %al, %al 
    jne .L5 
    movl $0, %eax 
    leave 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3" 
    .section .note.GNU-stack,"",@progbits 

int i-4(%rbp)int j-8(%rbp) 。正如你所看到的int j没有重新分配或者什么。

+2

如果您应用**其他**优化规则,“不要根据不具代表性的测试用例进行归纳”,您的Knuth报价看起来会更严重。你的嵌套for循环没有任何效果,任何值得它编译的编译器都会将'f()'的两个版本优化为'return 0;',但这可能意味着编译器总是在更常见的地方生成相同的代码情况下,循环不平凡。 –

+0

它没有被优化,只是'返回0',但我明白你的观点。我做了一个编辑,谢谢。装配仍然是一样的。 –