2010-08-30 68 views
4

引用“的C++编程语言”(特别版,部分4.9.6,“对象和左值”),并且如众所周知的:关于范围和寿命

[...]的对象声明在函数被创建时,当其名称超出范围时会被创建并销毁

行!并在第4.9.4节中提到:

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

这一切听起来不错!

但我的问题是:如果一个(自动)变量在控制到达块的末尾时如何被销毁?还有一个子问题:实际上是这样吗?

例如:

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实际上是未定义的),但实际上在执行时没有效果(没有实际执行任何操作来销毁对象)?

谢谢!

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

编辑2:如果变量是具有析构函数的对象对我来说很清楚,那么我就会询问非对象变量。

+3

查看代码所做的一个更好的方法是将“b”作为其类的析构函数的对象。 – Pointy 2010-08-30 13:24:05

+1

这是一个很好的例子,说明为什么使用编译器告诉你什么是和不正确的C++是一个坏主意。 – 2010-08-30 14:04:19

+0

这实际上恰恰相反,因为我正在阅读TC++ PL以获得正确的定义,我正在尝试使用示例并在此处询问...... – 2010-08-30 14:11:50

回答

9

您的示例中的代码确实是不确定的行为,它会出现在像这样简单的例子来工作。但编译器可能会选择重用用于存储变量b的槽。或者由于函数调用而将数据压入堆栈,可能会导致数据被破坏。

+0

好,没有关于在堆栈边缘有这个变量并被函数调用覆盖的问题。 – 2010-08-30 13:30:29

+0

+1,用于解释为什么它是一个未定义的行为。 – 2010-08-30 14:20:21

5

在您的例子

std::cout << *c; 

是不确定的行为 - 你试图访问其使用寿命结束后的变量。它只是发生在内存地址仍然映射到程序地址空间,没有人已经过度记忆,所以它似乎工作。

你不应该依赖这个,你不应该写这样的代码。可能发生的情况是中断将暂停您的程序以让其他程序运行。如果发生这种情况,许多操作系统会将当前的CPU状态(寄存器值)保存到同一堆栈中,并导致以寿命结束覆盖临时对象。

2

时,它的名字超出范围

这很容易被证伪本地静态当遇到并摧毁它的定义是建立在一个函数声明的对象:

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

请注意,名称的范围是一个静态的编译时间概念。但是对象的生命周期是一个运行时的概念。因此,您可以完全获取已经销毁的对象的引用。编译器不可能在编译时保护你。如果此时对象被分配的存储持续时间也停止,则不能再对变量执行任何操作,因为内存不再保证存在。自动变量的存储持续时间只会持续到块的退出。有时一个对象会终止生命周期,但该对象所分配的存储仍然存在。如果您手动调用某个类的析构函数,或者在写入其他成员的情况下手动调用某个工会的活动成员,则情况属实。

销毁时间对RAII工作很重要。让我们举个例子,我们锁定一个互斥体

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

    /* do non-exclusive stuff */ 
} 
+0

我不是在谈论静态,我应该明确地说过。 – 2010-08-30 13:31:08

1

在您的示例中取消引用'c'是未定义的行为。变量'b'超出范围并被销毁。如果它仍然打印'999'(这就是为什么我认为你相信'b'没有被破坏),这只是你幸运(或不幸:))

1

参考一个对象在其生命期后已经结束的是“未定义的行为”IIRC。请记住,“未定义的行为”有一个恶劣的习惯,表现为“正常工作”。正如@Pointy在评论中提到的那样,使用类似std::string b("b");的示例而不是整数,您可能会看到完全不同的行为。

当一个范围被关闭时,对象的析构函数被执行,所以程序的状态被修改。 “未定义”部分起作用,因为标准允许并期望析构函数修改对象的状态 - 释放分配给成员的内存以及不分配内存的内存。但是,内存中的值可能会完全不变,因为它是整数的情况。