变量的作用域和生命周期之间有什么关系?如果一个变量超出范围,它的内存是否允许被另一个变量覆盖,或者是保留的空间,直到函数离开。
我之所以问,是因为我想知道下面的代码是否真的有效,或者 *p 是否可能是未定义的
foo() {
int *p;
{
int x = 5;
p = &x;
}
int y = *p;
}
什么是范围?
范围是可以访问变量的区域或代码段。
什么是一生?
生命周期是对象/变量处于有效状态的持续时间。
因为,自动/局部非静态变量Lifetime
仅限于它们的Scope
.
换句话说,一旦创建它们的作用域( {
, )结束,自动变量就会自动销毁。}
因此名称自动开始。
您的代码示例有什么问题?
所以是的,你的代码有一个未定义的行为。
在您的示例中,范围*p
是创建后的整个函数体。
然而,x
它是一个非静态的局部/自动变量,因此它的生命周期x
结束于它的范围,即}
它创建的右括号,一旦范围结束x
不存在。*p
指向不再存在的东西。
请注意,技术上x
不存在超出其范围的情况,但可能会发生编译器没有删除的内容,x
并且可以x
通过指针访问超出其范围的内容(如您所做的那样)。但是,执行此操作的代码不是有效的 C++ 代码。它是调用未定义行为的代码。这意味着任何事情都可能发生(您甚至可能会看到x
完好无损的价值),人们不应该期望从这样的代码中观察到可观察的行为。
对于标识符指定的每个不同实体,标识符仅在称为其范围的程序文本区域内可见(即,可以使用)。由同一标识符指定的不同实体要么具有不同的作用域,要么位于不同的名称空间中
.
对象的生命周期是程序执行期间保证为其保留存储的部分。
在您的情况下x
,它是块本地的,它的生命周期也是如此,因此x
不能在块外通过其名称访问,也因为它的生命周期仅限于其块,x
离开块后将不再保留其地址,因此将导致未定义的行为。
另一方面,例如,一个static
局部变量,在这种情况下,作用域是块的本地变量,因此我们不能在块之外通过它的名称访问,但生命周期是整个程序,所以我们可以使用程序运行时变量在程序中任何位置的地址。这个例子应该有助于获得差异。
对象(即存储值的实际底层事物)具有生命周期。
变量(即用于引用对象的事物)具有范围。
无论哪种方式,都会y = *p
调用未定义的行为;引用的对象x
是自动x
的,超出范围时其生命周期结束。
变量的作用域和生命周期之间有什么关系?
正如 Oli 所说,变量具有范围和对象的生命周期。变量的范围和它的生命周期是绑定的,但是您可以拥有生命周期超出创建它们的范围的对象:
int* f() {
int *p // Variable p
= new int(1); // object (call it x)
return p; // p scope ends, p lifetime ends, x survives
}
int main() {
int *q // Variable q
= f(); // ... points to x
delete q; // x lifetime ends, q is in scope, q is alive
}
在您的特定情况下,x
变量在创建它的范围关闭时结束它的生命周期,因此您有未定义的行为。
如果一个变量超出范围,它的内存是否允许被另一个变量覆盖,或者是保留的空间,直到函数离开。
这是实现的一个细节。在所有情况下访问变量都是未定义的行为,因为并非所有情况都必须相等,并且如果变量具有非平凡的析构函数,它将在作用域的末尾被调用,因此内存是否存在是无关紧要的:对象不再存在。话虽如此,在许多情况下,当变量超出范围时,编译器不会释放函数中的内存(即它们不会重置帧指针),但它们可能会重用相同的空间来保存同一函数中的其他变量。
我已经运行了你的程序,输出是 5。输出仍然是 5,尽管 x 变量在第二个 '}' 之后被破坏是因为内存位置没有被任何其他变量覆盖。如果你有很多代码之后第二个作用域的结尾,很有可能之前由“x”拥有的内存位置被覆盖。
int x = 5;
*p = &x;
} // x lifetime ends after this.but the number '5' is still there in that memory.
//but it is possible to assign this memory (which previously belong to x) to a new var.
标准定义一旦离开范围就允许覆盖内存。编译器实现使其保留和释放。编译时优化可能会移动堆栈分配,因此它们将不再反映您的顺序。
您的代码确实会调用未定义的行为,因此不应使用。您的代码实际上可能有效,因为 y 的写入值发生在取值 p 指向之后。