Указатели в С++ после удаления

Прочитав много сообщений об этом, я хочу уточнить следующий вопрос:

A* a = new A();
A* b = a;

delete a;

A* c = a; //illegal - I know it (in c++ 11)
A* d = b; //I suppose it legal, is it true?

Итак, вопрос заключается в использовании значения копии удаленных указателей.

Я читал, что в С++ 11 чтение значения a приводит к undefined behavour - но как насчет считывания значения b?

Попытка прочитать значение указателя (примечание: это отличается от разыменование его) вызывает поведение, определяемое реализацией, поскольку С++ 14, который может включать в себя создание сбоя во время выполнения. (В С++ 11 это было undefined) Что происходит с самим указателем после удаления?

Ответ 1

И

A* c = a;
A* d = b;

- undefined в С++ 11 и реализация, определенная в С++ 14. Это связано с тем, что a и b являются "недопустимыми значениями указателя" (поскольку они указывают на освобожденное пространство памяти), а "использование недопустимого значения указателя" - это либо undefined, либо реализация определена в зависимости от версии С++. ( "Использование" включает в себя "копирование значения" ).

Соответствующий раздел ([basic.stc.dynamic.deallocation]/4) в С++ 11 читает (выделено мной):

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

с ненормативной запиской:

В некоторых реализациях он вызывает сгенерированное системой время выполнения

В С++ 14 в том же разделе говорится:

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

с ненормативной запиской:

Некоторые реализации могут определять, что копирование недопустимого значения указателя вызывает системную ошибку выполнения

Ответ 2

Эти две строки не имеют никакой разницы (что означает законность для С++):

A* c = a; //illegal - I know it (in c++ 11)
A* d = b; //I suppose it legal, is it true?

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

Ответ 3

Вы не должны использовать указатель после delete. Мой приведенный ниже пример с поддержкой a основан на реализации, определяемом реализацией. (спасибо M.M и Mankarse за указание на это)

Я считаю, что здесь важна не переменная a (или b, c, d), а то, что значение (= адрес памяти освобожденного блока), который в некоторых реализация может вызвать ошибку выполнения во время использования в некотором "контексте указателя".

Это значение может быть rvalue/expression, не обязательно значением, хранящимся в переменной, поэтому я не верю, что значение a когда-либо изменяется (я использую свободный контекст указателя), чтобы отличать от использования того же значение, то есть тот же набор бит, в выражениях, не связанных с указателем, - которые не будут вызывать ошибку времени выполнения).

------------ Мой оригинальный пост ниже. -----------

<ы > Ну, ты почти там с экспериментом. Просто добавьте cout как здесь:

class A {};
A* a = new A();
A* b = a;
std::cout << a << std::endl;   // <--- added here
delete a;
std::cout << a << std::endl;   // <--- added here. Note 'a' can still be used! 
A* c = a; 
A* d = b; 

Вызов delete a ничего не делает для переменной a. Это всего лишь вызов библиотеки. Библиотека, которая управляет распределением динамической памяти, сохраняет список выделенных блоков памяти и использует значение, переданное переменной a, чтобы пометить один из ранее выделенных блоков как освобожденный.

Хотя верно то, что Mankarse цитирует из документации на С++, о: "рендеринг недопустимых всех указателей, относящихся к любой части освобожденного хранилища" - обратите внимание, что значение переменной a остается нетронутым (вы не передали его по ссылке, но по значению!).

Итак, подведем итоги и постараемся ответить на ваш вопрос:

Переменная a по-прежнему существует в области после delete. Переменная a по-прежнему содержит одно и то же значение, которое является адресом начала выделенного (а теперь уже освобожденного) блока памяти для объекта class A. Это значение a технически может быть использовано - вы можете, например, напечатайте его, как в моем примере выше, - однако его трудно найти для него более разумным, чем печать/регистрация прошлого... То, что вы не должны делать, это попытаться удалить ссылку на это значение (которое вы также сохраняете в переменных b, c и d) - поскольку это значение больше не является допустимым указателем на память.С >

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