关于范围和寿命

时间:2010-08-30 13:21:09

标签: c++

引用“C ++编程语言”(特别版,第4.9.6节“对象和左值”),众所周知:

  

[...]函数中声明的对象在遇到其定义时被创建,并在其名称超出范围时被销毁

好的!在4.9.4节中:

  

名称只能用于程序文本的特定部分。对于在函数中声明的名称,该范围从其声明点延伸到其声明发生的块的末尾。

这一切听起来都不错!

但我的问题是:当控件到达块的末尾时,如何销毁(auto)变量?一个子问题:实际情况如此吗?

例如:

int main()
{
  int* c = 0;
  {
    int b = 999;
    c = &b;
  } // End of the scope of b...
  std::cout << b; // ... so this is illegal
  // But ...
  std::cout << *c; // ... is OK, so 'b' has not really been destroyed
}

我理解在退出函数范围时会破坏局部变量,因为函数调用涉及堆栈相关的事情。但是当退出一个简单的{ // ... }块时,没有任何反应。

然后它是一种特定于语言导致未定义的行为(在我的情况下,最后cout实际上是未定义的)但实际上在执行时没有效果(实际上没有执行任何操作来销毁对象)?< / p>

谢谢!

编辑1:我不考虑静态变量。

编辑2:如果变量是带有析构函数的对象,我很清楚,我问的是非对象变量。

5 个答案:

答案 0 :(得分:9)

示例中的代码确实是未定义的行为,它似乎适用于这样的简单示例。但是编译器可以选择重用用于存储变量b的槽。或者由于函数调用而导致数据被压入堆栈,因此可能会将其销毁。

答案 1 :(得分:5)

在你的例子中

std::cout << *c;

是未定义的行为 - 您尝试访问生命周期已结束的变量。只是发生了内存地址仍然映射到程序地址空间而没有人占用该内存,因此它似乎正在工作。

你不应该依赖它,你永远不应该写那样的代码。可能发生的情况是会发生中断以暂停程序以允许其他程序运行。如果发生这种情况,许多操作系统会将当前的CPU状态(寄存器值)保存到同一堆栈中,这会导致覆盖临时终止的临时状态。

答案 2 :(得分:2)

  

在函数中声明的对象在遇到其定义时创建,并在其名称超出范围时被销毁

对于当地静力学来说,这很容易被证明:

void f() {
  static string s = "";
} // out of scope, but still alive!

请注意,名称的范围是静态的编译时概念。但是对象的生命周期是运行时概念。所以你可以完全得到一个已经被破坏的对象的引用。编译器无法在编译时保护您免受此攻击。如果此时分配的对象的存储持续时间也已停止,则您不能再对变量执行任何操作,因为不再保证存在内存。自动变量的存储持续时间仅持续到其块的退出。有时,对象会终止生命周期,但分配对象的存储仍然存在。如果您在写入另一个成员时手动调用类的析构函数或者为union的活动成员,则会出现这种情况。

破坏时间对RAII的工作非常重要。我们举个例子来锁定一个互斥锁

void f() {
  { 
    lock x(mutex);
    /* do something */
  } // lock destroyed => mutex unlocked

  /* do non-exclusive stuff */
}

答案 3 :(得分:1)

在您的示例中取消“c”是未定义的行为。变量'b'超出范围并已被销毁。如果它仍然打印'999'(这就是为什么我认为你认为'b'没有被销毁的原因),那就是你变得幸运(或者不幸:)()

答案 4 :(得分:1)

在生命终结之后引用一个对象是“未定义的行为”IIRC。请记住,“未定义的行为”有一种讨厌的习惯,即将自己展现为“工作正常”。正如@Pointy在评论中提到的那样,使用std::string b("b");之类的内容作为示例而不是整数,您可能会看到完全不同的行为。

关闭作用域时,将执行对象的析构函数,以便修改程序的状态。 “未定义”部分发挥作用,因为标准允许并期望析构函数修改对象的状态 - 释放分配给成员的内存,而不是。但是,内存中的值可能完全不变,就像你的整数一样。