Использование удаленного адреса указателя

(*) Насколько я знаю, стандарт позволяет реализации модифицировать операнд оператора delete, однако большинство реализаций этого не делают.

int* ptr = new int(0);
delete ptr; //delete is allowed to modify ptr, for example set it to 0
std::cout << ptr; // UB?

Подтверждение (*), является ли определение ptr (в форме его печати) четким?

Если delete изменяет ptr, разрешено ли задавать значение ловушки, которое сделает чтение ptr UB?

Ответ 1

В С++ 14 это поведение, определяемое реализацией, [basic.stc.dynamic.deallocation]/4:

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

Отказ от недопустимого значения указателя и передача недопустимого значения указателя функции деаллокации имеют поведение undefined. Любое другое использование недопустимого значения указателя имеет поведение, определенное реализацией.

Есть сноска:

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

Это изменилось с С++ 11, где полужирный текст сказал "undefined поведение" и не было сноски.


Итак, чтобы ответить на ваш вопрос, delete ptr; разрешено установить значение ловушки, которое вызовет ошибку времени выполнения для std::cout << ptr. Документация компилятора должна указывать поведение. Это более узкое ограничение, чем UB, и в этом случае допустимо любое нестабильное поведение.

Ответ 2

В этом примере std::cout << ptr по умолчанию не выполняется undefined, потому что ptr не разыменовывается вообще, поэтому не имеет значения, на что его значение действительно установлено.

По умолчанию STL не определяет указатели operator<< для int*. Он определяет:

  • a operator<< для (signed|unsignd) char*, используемый для печати текста с нулевым завершением.

  • общий operator<< для void*, который просто печатает сам адрес памяти, на который установлен указатель, а не на данные, на которые указывает.

Так как int* неявно конвертируется в void*, вызов std::cin << ptr на самом деле вызывает operator<<(std::cin, (void*)ptr), и поэтому печатает адрес памяти как есть: ptr.

Код будет иметь поведение undefined только в том случае, если ваше приложение определяет свой собственный operator<< для int*, а затем пытается разыменовать указатель после его удаления.