变量声明成本是否可以大规模忽略?
Is variable declaration cost neglectable on large scale?
我偶然发现了我老师写的一段代码:
int i;
for(i = 0; i < 10; i++){
//...
}
//...
for(i = 0; i < 10; i++){
//...
}
相反,我会写成:
for(int i = 0; i < 10; i++){
//...
}
//...
for(int i = 0; i < 10; i++){
//...
}
以我的方式编写它,用于存储变量 i
的内存在两个循环之间被释放。我了解到我们必须使用尽可能小的范围来增强我们程序的 space 复杂性。
我想知道:int i
操作本身(变量 declaration)是否使用内存?这是唯一可以解释我老师的方法的原因。
所以如果变量声明 有实际的内存成本,它如何随着规模的扩大而演变?两种方式中的一种总是比另一种更好,还是随着上下文而改变?
does the int i operation itself (the variable declaration) uses
memory?
它将使用 4 或 8 字节的堆栈 space(取决于 int
在您的主机硬件上是 4 字节还是 8 字节长)。然而,它会使用相同数量的 space ,无论你是在 for 行上方还是在其内部声明它 。所以从内存使用的角度来看,它没有什么不同。 (请注意,就 CPU 周期而言,在堆栈上分配 4 或 8 个字节实际上是零成本,因为它所需要的只是将堆栈指针寄存器的值增加一个常数)
从代码正确性的角度来看,这种方法更可取:
for (int i=0; i<10; i++) {
[...]
}
... 因为这样一来,如果您在 for 循环主体结束后不小心尝试使用变量 i
,它将被捕获为编译时错误(这通常是您想要,因为 for 循环变量的值在 for 循环结束后通常没有用)
That's the only reason that would explain my teacher's way-to-go.
旧版本的 C 不支持“在 for 行内声明变量”语法,因此如果您的老师很久以前学习编程,他可能会在 for 行外声明他的变量从那是唯一选择的日子开始养成的习惯。
不要太好奇编译器会如何翻译你的源代码!优化编译器可以为两个版本构建相同的机器代码。
重要的是代码是否可读和健壮。这就是为什么最佳实践建议对变量使用尽可能小的范围的原因。如果您尝试在其范围之外错误地使用该变量,编译器将立即阻塞并为您节省数小时的调试时间。
但是节省 4 字节内存的可能性和节省变量分配成本都不重要。至少对于日常编程。它可能只在 低级优化 中很重要,但这只应考虑用于低资源嵌入式系统或当分析已明显成为瓶颈时。长话短说早期低级优化是不好的做法。
第一个 C 编译器要求自动对象声明在函数内的任何其他可执行代码之前(参见 https://www.bell-labs.com/usr/dmr/www/cman.pdf 第 15 页)。请注意,只有静态持续时间的对象才允许具有初始值设定项,并且不允许复合语句引入新对象。这些限制极大地简化了单遍编译,并且非常符合 Dennis Ritchie 的 objective,即 C 是一种易于编译的语言。
到 1989 年第一个 C 标准发布时,大多数编译器已经扩展了该语言以允许任何复合语句以自动对象声明开头,并允许自动对象声明包含初始化程序。这些添加有时会使单遍编译器生成高效代码变得更加困难,但生成正确代码通常并不难。例如,给定如下内容:
int test()
{
int i,j;
i=foo();
j=bar();
编译器本来可以很容易地使用一条指令为 i
和 j
保留堆栈 space,但是如果代码写成:
int test()
{
int i=foo(),j=bar();
单程编译器可能必须生成一条指令,为 i
分配堆栈 space,然后调用 foo
,然后是分配堆栈 space 的指令] 为 j
,然后调用 bar
。不如使用旧语法产生的效率高,但增加分配更多空间的能力 space 并不是一个特别的问题。
虽然 C89 允许某些可执行代码先于自动对象声明,但它要求复合语句块中的任何对象声明先于该块中的任何其他语句。 C99 标准放宽了这条规则,还允许 for
循环的第一个原因是自动对象声明而不是表达式。尽管有些机器无法使用 C99 编译器,但将循环索引声明为 for
循环的一部分的能力非常有用,因此通常希望以这种方式编写代码。
使用该形式的最大缺点是,如果一个块中有两个循环,则有必要更改其中一个或周围的代码,以便为索引对象分配循环或在循环之后进行检查,许多编译器会对这样的模式发出尖叫:
void test(void)
{
for (int i=0; i<50; i++)
doSomethingWith(i);
int i;
for (i=; i<50 && shouldntEarlyExit(i); i++)
doSomethingWith(i);
doSomethingWithWhatWoudlHaveBeenNextValueOf(i);
}
尽管标准允许名称 i
既用作范围限于第一个循环的自动对象的名称,也用作范围为封闭块的自动变量,许多编译器会发出有关此类名称重用的警告,因为它通常是编程错误的结果。因此,如果有必要重写任何循环以便在循环之前加载索引值或在循环之后检查索引值,则可能需要从使用该名称的所有循环中删除声明。在函数开始时声明索引而不是在每个循环开始时声明索引将避免以后需要从循环语句中删除声明。
我偶然发现了我老师写的一段代码:
int i;
for(i = 0; i < 10; i++){
//...
}
//...
for(i = 0; i < 10; i++){
//...
}
相反,我会写成:
for(int i = 0; i < 10; i++){
//...
}
//...
for(int i = 0; i < 10; i++){
//...
}
以我的方式编写它,用于存储变量 i
的内存在两个循环之间被释放。我了解到我们必须使用尽可能小的范围来增强我们程序的 space 复杂性。
我想知道:int i
操作本身(变量 declaration)是否使用内存?这是唯一可以解释我老师的方法的原因。
所以如果变量声明 有实际的内存成本,它如何随着规模的扩大而演变?两种方式中的一种总是比另一种更好,还是随着上下文而改变?
does the int i operation itself (the variable declaration) uses memory?
它将使用 4 或 8 字节的堆栈 space(取决于 int
在您的主机硬件上是 4 字节还是 8 字节长)。然而,它会使用相同数量的 space ,无论你是在 for 行上方还是在其内部声明它 。所以从内存使用的角度来看,它没有什么不同。 (请注意,就 CPU 周期而言,在堆栈上分配 4 或 8 个字节实际上是零成本,因为它所需要的只是将堆栈指针寄存器的值增加一个常数)
从代码正确性的角度来看,这种方法更可取:
for (int i=0; i<10; i++) {
[...]
}
... 因为这样一来,如果您在 for 循环主体结束后不小心尝试使用变量 i
,它将被捕获为编译时错误(这通常是您想要,因为 for 循环变量的值在 for 循环结束后通常没有用)
That's the only reason that would explain my teacher's way-to-go.
旧版本的 C 不支持“在 for 行内声明变量”语法,因此如果您的老师很久以前学习编程,他可能会在 for 行外声明他的变量从那是唯一选择的日子开始养成的习惯。
不要太好奇编译器会如何翻译你的源代码!优化编译器可以为两个版本构建相同的机器代码。
重要的是代码是否可读和健壮。这就是为什么最佳实践建议对变量使用尽可能小的范围的原因。如果您尝试在其范围之外错误地使用该变量,编译器将立即阻塞并为您节省数小时的调试时间。
但是节省 4 字节内存的可能性和节省变量分配成本都不重要。至少对于日常编程。它可能只在 低级优化 中很重要,但这只应考虑用于低资源嵌入式系统或当分析已明显成为瓶颈时。长话短说早期低级优化是不好的做法。
第一个 C 编译器要求自动对象声明在函数内的任何其他可执行代码之前(参见 https://www.bell-labs.com/usr/dmr/www/cman.pdf 第 15 页)。请注意,只有静态持续时间的对象才允许具有初始值设定项,并且不允许复合语句引入新对象。这些限制极大地简化了单遍编译,并且非常符合 Dennis Ritchie 的 objective,即 C 是一种易于编译的语言。
到 1989 年第一个 C 标准发布时,大多数编译器已经扩展了该语言以允许任何复合语句以自动对象声明开头,并允许自动对象声明包含初始化程序。这些添加有时会使单遍编译器生成高效代码变得更加困难,但生成正确代码通常并不难。例如,给定如下内容:
int test()
{
int i,j;
i=foo();
j=bar();
编译器本来可以很容易地使用一条指令为 i
和 j
保留堆栈 space,但是如果代码写成:
int test()
{
int i=foo(),j=bar();
单程编译器可能必须生成一条指令,为 i
分配堆栈 space,然后调用 foo
,然后是分配堆栈 space 的指令] 为 j
,然后调用 bar
。不如使用旧语法产生的效率高,但增加分配更多空间的能力 space 并不是一个特别的问题。
虽然 C89 允许某些可执行代码先于自动对象声明,但它要求复合语句块中的任何对象声明先于该块中的任何其他语句。 C99 标准放宽了这条规则,还允许 for
循环的第一个原因是自动对象声明而不是表达式。尽管有些机器无法使用 C99 编译器,但将循环索引声明为 for
循环的一部分的能力非常有用,因此通常希望以这种方式编写代码。
使用该形式的最大缺点是,如果一个块中有两个循环,则有必要更改其中一个或周围的代码,以便为索引对象分配循环或在循环之后进行检查,许多编译器会对这样的模式发出尖叫:
void test(void)
{
for (int i=0; i<50; i++)
doSomethingWith(i);
int i;
for (i=; i<50 && shouldntEarlyExit(i); i++)
doSomethingWith(i);
doSomethingWithWhatWoudlHaveBeenNextValueOf(i);
}
尽管标准允许名称 i
既用作范围限于第一个循环的自动对象的名称,也用作范围为封闭块的自动变量,许多编译器会发出有关此类名称重用的警告,因为它通常是编程错误的结果。因此,如果有必要重写任何循环以便在循环之前加载索引值或在循环之后检查索引值,则可能需要从使用该名称的所有循环中删除声明。在函数开始时声明索引而不是在每个循环开始时声明索引将避免以后需要从循环语句中删除声明。