2015-02-25 2 views
7

я не могу найти, где в стандарте, что он говорит, что эта программа не определена:Является ли это неопределенным поведением для разыменования висячего указателя?

#include <iostream> 

int main() 
{ 
    int *p; 
    { 
     int n = 45; 
     p = &n; 
    } 
    std::cout << *p; 
} 

Ни в одном из случаев в §3.8 жизни объекта, кажется, применимы и здесь.

+2

Я считаю, что 'p' все равно укажет на место памяти, где' n' было, но вы не знаете, что там будет. – mstbaum

+0

@mstbaum и как это связано с вопросом? – Slava

+0

@remyabel (Мой предыдущий удаленный комментарий спросил, не означает ли «предсказать результаты» UB.) Возможно, я не понимаю, что такое неопределенное поведение. Мое рассуждение, вероятно, похоже на mstbaum's, поскольку мы не знаем, что находится в этом месте в памяти, поэтому мы не можем предсказать результаты. Разве этого недостаточно? Должен ли я искать его в стандарте, чтобы быть уверенным? – eigenchris

ответ

6

Я не уверен на 100% из-за формулировки, но похоже, что это покрывается 3.8/6 (причина, по которой эта интерпретация верна, объясняется ненормативным примером в 3.8/5, // undefined behavior, lifetime of *pb has ended) :

... после того, как срок службы объекта закончился, и перед хранением, которое объект занят, повторно используется или выпущен, может использоваться любое значение, относящееся к исходному объекту, но только ограниченным образом ... . Программа имеет неопределенное поведение, если:

Тогда первой пулей является виновник: an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,: Это преобразование должно произойти либо в точке вызова operator<<, либо, наконец, в точке, где интегральное значение считывается для форматирования в пределах кода ostream.

+0

Не знаю, где это находится в стандарте, но так как n живет в стеке, можно подумать, что память будет «выпущена», когда она выйдет из сферы действия. –

+0

Кроме того, мое понимание 3.7.3 («время автоматического хранения») заключается в том, что хранилище, в котором объект, занятый объектом, не требуется, существует после выхода блока. –

+1

Можете ли вы определить, когда хранилище * повторно используется или выпущено *, поскольку оно зависит от этой фразы. Тем более, что это требует этого * до *. –

-1

Итак, прежде всего в соответствии с 3.7.3 Автоматическая продолжительность хранения хранения вашего объекта выделяется:

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

И от жизни 3.8 Object

До жизни объекта началось, но после хранения которой объект будет занимать было выделено или, после того, как время жизни объекта имеет и до хранения, который занятый объект используется повторно или освобожден, может использоваться любой указатель, который относится к месту хранения , где находится или находится объект, , но только в ограниченных количествах

так разыменования указателя на переменную, которая хранения выпущенном приводит к UB

1

Это, конечно, неопределенное поведение (по здравому смыслу, и формулировки стандарта).

Насколько стандарт идет, 3,8/5 довольно конкретно о том, что разрешено и о том, что это не так:

[...] после того, как время жизни объекта закончилась и перед тем хранилище, в котором объект занят, повторно используется или освобождается, любой указатель, который ссылается на место хранения, в котором находится или находится объект, может использоваться, но только ограниченным образом [...] и с использованием указателя, как если бы указатель были типа void *, четко определен.
[...] разрешено [...], как описано ниже.Программа имеет неопределенное поведение, если:
- ...
- [...] используются в качестве операнда static_cast, за исключением того, когда преобразование является указатель на сорт void, или указатель на сорта void и впоследствии указатель либо сорта или сорта charunsigned char
- [...] используется в качестве операнда dynamic_cast

хранения объекта заканчивается в конце области на 3.7.3/1 (на практике это, скорее всего не верно, фрейм стека, вероятно, будет сброшен в конце functio n, но формально вот что бывает). Следовательно, разыменование не происходит после окончания срока службы , но до освобождение хранилища. Это происходит после освобождение памяти.
Особые условия, при которых вы можете разыменовать указатель, в любом случае не применяются (то же самое верно для любых аналогичных абзацев с тем же предварительным условием, как 3.8/6).

Далее, при условии, что предыдущий пункт не соответствует действительности, это допустимо только для разыменования указателя в качестве сорта void* или бросить его сорта char (знаком или без знака) до разыменования. Другими словами, вы находитесь , а не разрешено смотреть на int, как если бы это было int. Как указано в 3.8/5, int* действительно всего лишь void* после времени жизни объекта. Это означает, что разыменование его как int* эквивалентно выполнению броска (не явно, но все же).

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

1

*p является glvalue. Код cout << *p требует преобразования lvalue-to-rvalue. Это определяется C++ 14 [conv.lval].

В пункте 2 перечислены различные случаи и описывается поведение в каждом случае. Ни один из них не применяется к *p. В частности, последний пункт:

В противном случае значение, содержащееся в объекте, обозначенном glvalue, является результатом prvalue.

Однако *p не указывает объект.

В разделе [basic.life] представлены несколько случаев, определяющих, какое преобразование lvalue-to-rval делает, помимо того, что сказано в [conv.lval]. Эти случаи связаны с тем, когда хранилище для объекта было получено, но мы находимся за пределами жизни объекта. Однако они не применяются к *p, поскольку хранилище освобождается при завершении предыдущего блока.

Таким образом, поведение этого кода неопределенное бездействием: нигде в Стандарте не не определяет, что значит выполнить RValue преобразования, когда именующий не указует на объект и не указует на допустимое для хранения объекта.


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

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