循环中的变量定义

3

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

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

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

例如,写成这样是否更好:

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

我并不是指这段代码,而是任何类似的循环。

我用gcc 4.7.2做了一个简单的测试,测试中有一个与这个不同的循环,但是生成了相同的汇编代码。我的问题是,按照标准,这两个循环是否相同?


正如你自己所说的关于第一个循环:“我知道标准非常明确,即x仅存在于循环内部”,并且在第二个示例中,变量x在循环之后可用,这两个示例不可能相同,否则标准将是模棱两可的。 - Some programmer dude
@JoachimPileborg:嗯,是的 - 但就循环而言。 - teppic
如果你只考虑循环而不考虑其他因素,那么是的,它们是相同的。 - Some programmer dude
4个回答

6
请注意,像这样“分配”自动变量几乎是免费的;在大多数机器上,它要么是单指令堆栈指针调整,要么编译器使用寄存器,在这种情况下不需要执行任何操作。
此外,由于变量在循环退出之前仍然处于作用域中,因此绝对没有理由在循环退出之前“删除”(=重新调整堆栈指针)它。我当然不希望像这样的代码每次迭代都有任何开销。
当然,如果编译器觉得合适,它也可以将分配从循环中移出,使代码等效于第二个例子,其中while之前有一个int x;声明。重要的是,第一种方法更易于阅读,并且更紧密地定位,即更适合人类。

但是,由于变量在循环中被定义(而不仅仅是访问),它必须超出范围(至少从技术上来说)吧?否则下一次迭代将尝试定义现有变量的另一个实例? - teppic
1
从技术上讲,它会消失。在实践中,这意味着(a) 你不能在循环外引用 x,(b) 编译器停止使用为 x 分配的空间来存储 x(并且它可能开始使用同一空间来存储后续代码中不同的变量)。除了编译器是否引用内存位置之外,不太可能有其他变化。 - Jonathan Leffler

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

孤立的两个片段等效,但区别在于x的作用域。在声明在循环外的示例中,该值在循环退出后仍然存在。在循环内声明x时,一旦循环退出,它就无法访问。如果您写:

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

那么这两个片段几乎是等价的。在汇编级别上,你很难发现任何一个案例中的差异。


你对于 x 的作用域的差异是正确的,当然,我之前的问题描述不够清晰,但是你的例子解决了这个比较。 - teppic

1
我的个人观点是,一旦你开始担心这样的微小优化,你就注定要失败。收益是:
a)可能非常小
b)不可移植
我会坚持使用能清晰表达你意图的代码(即在循环内部声明x),让编译器关心效率。

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);
        }
}

编译器很可能只会在函数开始时分配一个大的堆栈帧。相比于在块中创建和销毁变量,仅在函数开始时分配所需的所有内容更难生成代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接