3

我一直在努力养成在需要时定义琐碎变量的习惯。我一直对编写这样的代码持谨慎态度:

while (n < 10000) {
   int x = foo();
   [...]
}

我知道标准是绝对明确的,x只存在于循环内部,但这在技术上是否意味着整数将在每次迭代时在堆栈上分配和释放?我意识到优化编译器不太可能做到这一点,但它保证吗?

例如,这样写是不是更好:

int x;
while (n < 10000) {
   x = foo();
   [...]
}

我不是专门指这段代码,而是指像这样的任何循环。

我用 gcc 4.7.2 对一个以这种方式不同的简单循环进行了快速测试,并生成了相同的程序集,但我的问题是,根据标准,这两个真的是相同的吗?

4

4 回答 4

6

请注意,像这样“分配”自动变量几乎是免费的;在大多数机器上,它要么是单指令堆栈指针调整,要么编译器使用寄存器,在这种情况下不需要做任何事情。

此外,由于变量在循环退出之前保持在范围内,因此绝对没有理由“删除”(=重新调整堆栈指针)直到循环退出,我当然不希望代码每次迭代都会有任何开销像这样。

此外,如果编译器愿意的话,当然可以自由地将分配完全“移出”循环,使代码等同于您的第二个示例,int x;其中while. 重要的是,第一个版本更易于阅读且本地化程度更高,即更适合人类使用。

于 2013-03-14T13:11:48.893 回答
1

是的,循环内的变量x在技术上是在每次迭代中定义的,并通过foo()对每次迭代的调用进行初始化。如果foo()每次产生不同的答案,这很好;如果每次都产生相同的答案,这是一个优化机会——将初始化移出循环。对于像这样的简单变量,编译器通常只sizeof(int)在堆栈上保留字节——如果它不能保存x在寄存器中——它用于x何时x在范围内,并且可以将该空间重用于同一函数中其他地方的其他变量。如果变量是 VLA(可变长度数组),那么分配会更复杂。

两个单独的片段是等价的,不同的是x. 在循环外部声明的示例中x,该值在循环退出后仍然存在。在x循环内声明后,一旦循环退出就无法访问。如果你写:

{
    int x;
    while (n < 10000)
    {
        x = foo();
        ...other stuff...
    }
}

那么这两个片段足够接近。在汇编程序级别,您将很难发现任何一种情况的差异。

于 2013-03-14T13:16:47.403 回答
1

我个人的观点是,一旦你开始担心这种微优化,你就注定要失败。收获是:

a) 可能非常小

b) 非便携式

我会坚持使用使您的意图清晰的代码(即在循环内声明 x )并让编译器关心效率。

于 2013-03-14T13:25:56.137 回答
0

C 标准中没有任何内容说明编译器在这两种情况下应如何生成代码。如果需要,它可以在循环的每次迭代中调整堆栈指针。

话虽如此,除非你开始用这样的 VLA 做一些疯狂的事情:

void bar(char *, char *);
void
foo(int x)
{
        int i;
        for (i = 0; i < x; i++) {
                char a[i], b[x - i];
                bar(a, b);
        }
}

编译器很可能只会在函数开头分配一个大堆栈帧。生成代码来创建和销毁块中的变量而不是在函数的开头分配你需要的所有东西更难。

于 2013-03-14T13:31:43.183 回答