我一直在努力养成在他们需要的时候定义琐碎变量的习惯。我一直对编写这样的代码持谨慎态度:
while (n < 10000) {
int x = foo();
[...]
}
我知道标准是绝对清楚的,x
只存在于循环内部,但这在技术上是否意味着整数将在每次迭代时在堆栈上分配和解除分配?我意识到优化编译器不太可能这样做,但它有保证吗?
例如,写作是否更好:
int x;
while (n < 10000) {
x = foo();
[...]
}
我不是故意用这个代码,而是在任何类似的循环中。
我用gcc 4.7.2快速测试了一个简单的循环,这种方式不同,并且生成了相同的程序集,但我的问题是这两个,根据标准,是相同的吗?
答案 0 :(得分:6)
请注意,“分配”这样的自动变量几乎是免费的;在大多数机器上,它可以是单指令堆栈指针调整,也可以是编译器使用寄存器,在这种情况下不需要进行任何操作。
此外,由于变量保持在范围内直到循环退出,所以绝对没有理由“删除”(=重新调整堆栈指针)它直到循环退出,我当然不希望有任何像这样的代码的每次迭代开销。
此外,当然,编译器可以自由地将分配完全“移出”循环,如果它感觉像是这样,在int x;
之前使代码与while
的第二个示例相同。重要的是,第一个版本更容易阅读,更加强烈的本地化,即对人类更好。
答案 1 :(得分:1)
是的,循环中的变量x
在技术上定义在每次迭代中,并在每次迭代时通过调用foo()
进行初始化。如果foo()
每次产生不同的答案,这很好;如果它每次都产生相同的答案,那么这是一个优化机会 - 将初始化移出循环。对于这样的简单变量,编译器通常只在堆栈上保留sizeof(int)
个字节 - 如果它不能将x
保留在寄存器中 - 它在x
时用于x
在范围内,并且可以将该空间重用于同一函数中其他位置的其他变量。如果变量是VLA - 可变长度数组 - 则分配更复杂。
单独的两个片段是等效的,但区别在于x
的范围。在循环外声明x
的示例中,循环退出后该值仍然存在。在循环内部声明x
后,一旦循环退出就无法访问。如果你写了:
{
int x;
while (n < 10000)
{
x = foo();
...other stuff...
}
}
然后这两个片段相当接近。在汇编程序级别,您将很难在任何一种情况下发现差异。
答案 2 :(得分:1)
我个人的观点是,一旦你开始担心这种微观优化,你就注定要失败。收益是:
a)可能非常小
b)非便携式
我坚持使用能够明确你的意图的代码(即在循环中声明x)并让编译器关注效率。
答案 3 :(得分: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);
}
}
编译器很可能只是在函数开头分配一个大堆栈帧。生成用于在块中创建和销毁变量的代码更难,而不是仅仅在函数开头分配所需的全部内容。