2010-08-30 2 views
4

Цитирование "C++ язык программирования" (специальное издание, раздел 4.9.6, "Объекты и Lvalues"), и как хорошо известно:О масштабах и жизни

[...] объект объявлен в функции создается, когда ее определение встречается и уничтожается, когда его имя выходит за пределы области действия

OK! И в разделе 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

+1

Это хороший пример того, почему плохая идея использовать компилятор, чтобы рассказать вам, что есть и что не так. C++. –

+0

На самом деле это совсем наоборот, поскольку я читаю TC++ PL, чтобы иметь правильные определения, которые я пытаюсь использовать с примерами и что я спрашиваю здесь ... –

ответ

9

код в вашем образце действительно неопределенное поведение, и оно появится на работу в простых примерах, как это. Но компилятор может выбрать повторное использование слота, используемого для хранения переменной b. Или он может быть уничтожен в результате передачи данных в стек из-за вызова функции.

+0

Хорошо, дело не в том, что эта переменная находится на краю стека и перезаписывается вызовом функции. –

+0

+1 для объяснения, почему именно это неопределенное поведение. –

5

В вашем примере

std::cout << *c; 

это неопределенное поведение - вы пытаетесь получить доступ к переменной, которая закончилась жизнь. Просто происходит, что адрес памяти по-прежнему отображается в адресное пространство программы, и никто не перезаписывает эту память, поэтому кажется, что она работает.

Вы не должны полагаться на это, и вы никогда не должны писать такой код. Возможно, произойдет прерывание, чтобы приостановить вашу программу, чтобы другие программы запускались. Если это происходит, многие операционные системы сохраняют текущее состояние процессора (регистрирует значения) в одном стеке, что приводит к перезаписи временного ресурса с завершением продолжительности жизни.

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

Я не говорил о статике, я должен был сказать это явно , –

1

Derefercing 'c' в вашем примере - неопределенное поведение. переменная 'b' выходит за пределы области действия и была уничтожена. Если он по-прежнему печатает «999» (именно поэтому я считаю, что вы считаете, что «b» не был уничтожен), вам просто повезло (или не повезло :))

1

Ссылаясь на объект после его жизни закончился «неопределенное поведение» IIRC. Помните, что «неопределенное поведение» имеет неприятную привычку демонстрировать себя как «отлично работает». Поскольку @Pointy упоминается в комментарии, используйте что-то вроде std::string b("b"); в качестве примера вместо целого, и вы, вероятно, увидите совершенно другое поведение.

Когда область видимости закрыта, деструкторы объектов выполняются, поэтому изменяется состояние программы. «Неопределенная» часть вступает в игру, потому что Стандарт позволяет и ожидает деструктора, чтобы изменить состояние освобождающей объект памяти, выделенной для членов, а что нет. Однако значение в памяти может быть полностью неизменным, как и в случае вашего целого.

Смежные вопросы