在循环完成执行后,是否在while或for循环中创建的变量是从内存中释放/删除的?另外,在循环中创建临时变量是一种不好的编码习惯吗?
在这个例子中,它是创建100个X变量然后处理它们,还是它们在每次迭代时处理?谢谢。 例如:
int cc =0;
while(cc < 100){
int X = 99; // <-- this variable
cc++;
}
答案 0 :(得分:20)
范围和生命周期是两回事。对于在没有static
的块范围内定义的变量,它们或多或少地紧密相关,但它们仍然是截然不同的概念 - 如果你不这样做,你可以用脚射击自己保持他们的直接。
从问题中引用代码段:
int cc =0;
while(cc < 100){
int X = 99; // <-- this variable
cc++;
}
X
的范围是程序文本的区域,其名称可见。它从它定义的点延伸到封闭块的末尾,封闭块由{
和}
字符分隔。 (块恰好是while
语句的一部分这一事实并不直接相关;它是定义范围的块本身。)
在块中,名称X
引用int
变量。在块外,名称X
不可见。
X
的生存期是程序执行期间X
逻辑上存在的时间。它在执行到达开始{
(在定义之前)时开始,并在执行到达结束}
时结束。如果块被执行100次,则创建X
并且&#34;销毁&#34; 100次,并且有100个不相交的生命周期。
虽然名称 X
仅在其范围内可见,但可以随时(直接或间接)访问名为X
的对象在其一生中。例如,如果我们将&X
传递给函数,那么该函数可以读取和更新对象,即使该函数完全超出其范围。
您可以获取X
的地址,并保存该地址,以便在其生命周期结束后使用 - 但这样做会导致未定义的行为。指向其生命周期已结束的对象的指针是 indeterminate ,并且任何取消引用它的尝试 - 甚至是引用指针的值 - 都有未定义的行为。
当对象到达其生命周期的末尾时,实际上不会发生任何事情。语言只需要对象在其生命周期中存在;除此之外,所有赌注都已关闭。分配用于保存对象的堆栈空间可能会被释放(这通常只是意味着移动堆栈指针),或者,作为优化,堆栈空间可能会保留分配并重新用于循环的下一次迭代。
另外,在循环中创建临时变量是不是很糟糕?
完全没有。只要您不在对象的生命周期结束时保存对象的地址,就没有任何问题。作为性能优化,分配和释放通常在进入和退出函数而不是块时完成。将变量限制在合理的范围内是非常良好的编码习惯。它通过限制代码不同部分之间不必要的交互,使代码更容易理解。如果在循环体内定义了X
,那么循环之外的任何东西都不会影响它(如果你没有做过太狡猾的事情),这样就可以更容易地推断出代码。
更新:如果X
属于具有非平凡构造函数和/或析构函数的类型,则实际上必须在进入和退出块时执行X
的创建和销毁(除非编译器能够优化该代码)。对于int
类型的对象,这不是问题。
答案 1 :(得分:6)
是的,变量被创建和销毁N次,除非编译器以某种方式对其进行优化(我相信它可以)。当你只有一个int
时,这不是什么大问题。当你在循环中重新创建99次复杂对象时,它会变得更有问题。
一个小实际例子:
#include <iostream>
using namespace std;
class TestClass
{
public:
TestClass();
~TestClass();
};
TestClass::TestClass()
{
cout<<"Created."<<endl;
}
TestClass::~TestClass()
{
cout<<"Destroyed"<<endl;
}
int main() {
for (int i = 0; i < 5; ++i)
{
TestClass c;
}
return 0;
}
在此代码中TestClass c
将重新创建5次。 IdeOne Demo
答案 2 :(得分:4)
是。范围{ }
中定义的任何变量:
if (true) { // Forgive me father for this heresy :)
int a = 0;
}
a = 1; // This will throw an error
一旦超出范围,就会自动解除分配。
在这种情况下也是如此:
for (int i = 0; i < 10; ++i) {
// Do stuff
...
}
i = 1; // This will throw an error
i
的范围仅限于for循环,即使它未在{ }
对中声明。
答案 3 :(得分:2)
如前所述,事实上,不需要重复创建/销毁局部变量。
这是不必要的CPU时间浪费。
示例,使用“gcc -S -masm = intel -fverbose-asm”编译
int crazyloop (int n) {
if (n > 0) {
int i;
for (i = 0; i < n; i++)
;
}
return n;
}
相应的装配清单是:
_Z9crazyloopi:
.LFB0:
.cfi_startproc
push rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp #,
.cfi_def_cfa_register 6
mov DWORD PTR [rbp-20], edi # n, n
cmp DWORD PTR [rbp-20], 0 # n,
jle .L2 #,
mov DWORD PTR [rbp-4], 0 # i,
.L4:
mov eax, DWORD PTR [rbp-4] # tmp89, i
cmp eax, DWORD PTR [rbp-20] # tmp89, n
jge .L2 #,
add DWORD PTR [rbp-4], 1 # i,
jmp .L4 #
.L2:
mov eax, DWORD PTR [rbp-20] # D.3136, n
pop rbp #
.cfi_def_cfa 7, 8
ret
.cfi_endproc
有趣的部分在于注册RBP的参考。 设置后,它不会改变。变量“i”始终位于[rbp-4]。 (代码的变化,更多的变量等,给出了相同的结果=没有重复的分配/解除分配=对堆栈顶部位置的修改)。
这是最明智的做法:想想一个循环数万次的循环。
其他编译器会以不同方式执行此操作吗可能是的,但是,为什么它会这样做呢?
安全可能是一个问题?不可能;在这种情况下,程序员应该在变量消失之前简单地覆盖变量。
答案 4 :(得分:1)
是
如果你想在循环之后访问变量,你应该声明它在循环之后
while(...) {
int i = 0;
i++; // valid
}
i++; // invalid, i doesnt exist in this context
循环之外的 i
int i = 0;
while(...) {
i++; // valid
}
i++; // valid
viariable的生命周期仅限于创建它的上下文{...}
我们在考虑一个对象时,在到达}
答案 5 :(得分:0)
是的,它们在超出范围时会被销毁。请注意,这不是特定于循环中的变量。此规则适用于automatic storage duration的所有变量。
答案 6 :(得分:0)
任何变量在其范围内保持活跃状态。在特定变量甚至不退出的范围之外,忘记访问其值。
for(;;)
{
int var; // Scope of this variable is within this for loop only.
}
// Outside this for loop variable `var` doesn't exits.
答案 7 :(得分:0)
在里面声明的变量将有自己的范围。 当你知道你不会在范围之外使用那个变量时,你可以使用它。 那个编译器的工作。 :)