Гарантированно ли указатель сохранит свое значение после `delete` в C ++?

На основе этого вопроса.

Предположим, что в коде C ++ у меня есть действительный указатель и правильно delete. Согласно стандарту C ++ указатель станет недействительным (3.7.3.2/4 - функция освобождения сделает недействительными все указатели, относящиеся ко всем частям освобожденного хранилища).

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

Гарантирует ли стандарт, что указатель сохранит свое значение, или значение может изменяться?


person sharptooth    schedule 15.02.2011    source источник
comment
Позволяет ли подпись delete получить доступ к указателю, то есть к чему-либо, кроме передачи по значению?   -  person Rup    schedule 15.02.2011
comment
Интересный вопрос, но, надеюсь, чисто академическое любопытство. Я не могу представить, зачем вам это нужно знать при написании кода.   -  person Cody Gray    schedule 15.02.2011
comment
@ Коди Грей: Ты прав. Использование указателя (например, попытка printf() его) после delete равно UB, поэтому пользователь не может даже юридически прочитать указатель и сравнить его с исходным значением.   -  person sharptooth    schedule 15.02.2011
comment
@Rup: Delete в данном случае - это оператор без подписи. (Оператор удаления - это функция, которая вызывается оператором удаления или вызывается явно.)   -  person Fred Nurk    schedule 15.02.2011
comment
Я не понимаю вопроса. Вы можете привести какой-нибудь пример?   -  person Harish    schedule 15.02.2011
comment
@Harish: Примерно так: Я delete указатель, и после этого он сохраняет 0xDEADBEEF адрес.   -  person sharptooth    schedule 15.02.2011
comment
@Cody Gray: Это зависит от информации, которую содержит указатель: (1) его фактический адрес и (2) куда он указывает. Иногда первое может быть важным - возможно, не должно - но могло. Интересный вопрос.   -  person Ole Thomsen Buus    schedule 15.02.2011
comment
@FredNurk: Было бы яснее сказать, что это ключевое слово, которое вызывает оператор (operator delete) для делегирования своей работы. И что у ключевого слова нет «подписи».   -  person Lightness Races in Orbit    schedule 15.02.2011
comment
Хотя на практике это не актуально, это интересный вопрос, кстати!   -  person Lightness Races in Orbit    schedule 15.02.2011
comment
@sharptooth Где хранит адрес?   -  person Harish    schedule 15.02.2011
comment
@Harish: в указателе, который был операндом delete.   -  person sharptooth    schedule 15.02.2011
comment
@Fred @Tomalak: Самый правильный способ обозначить это как выражение-удаление. Это выражение; не оператор, функция, делегированная функция и т. д. (Хотя, конечно, есть побочные эффекты, такие как возможный вызов operator delete и kin.)   -  person GManNickG    schedule 15.02.2011
comment
@sharptooth: Я понимаю. Единственный вопрос. Мне самому не терпится узнать ответ.   -  person Harish    schedule 15.02.2011
comment
@Cody Gray: это не чисто академический, по крайней мере в том смысле, что если значение изменяется, то строго соответствующая программа может заметить разницу. Вы не можете получить доступ к значению указателя как к значению указателя, но вы можете корректно сравнить значения байтов, которые он занимает, до и после.   -  person Steve Jessop    schedule 15.02.2011
comment
@ Стив Джессоп: Если я сохраню исходное значение, а затем memcmp() его со значением после delete, не будет ли это неопределенное поведение?   -  person sharptooth    schedule 15.02.2011
comment
@sharptooth: Я так не думаю. memcmp не использует значение указателя, насколько я читал стандарт. Это не неопределенное поведение для memcmp 4 байтов, которые оказались недействительными в качестве значения с плавающей запятой, так почему же оно должно быть неопределенным для memcmp 4 байтов, которые просто так оказались недопустимыми в качестве значения указателя? Память всегда можно рассматривать как байты, при условии, что она все еще выделена, не имеет значения, что она представляет или не представляет. Только не используйте значение указателя. Результат memcmp может быть неопределенным или определяемым реализацией, в зависимости от того, что стандарт говорит о delete для lvalue.   -  person Steve Jessop    schedule 15.02.2011
comment
@Tomalak: обратите внимание, что оператор delete уничтожает объект перед вызовом operator delete для освобождения памяти.   -  person fredoverflow    schedule 15.02.2011
comment
@GMan: Верно. / @FredOverflow: правда, он не делегирует всю свою работу.   -  person Lightness Races in Orbit    schedule 16.02.2011


Ответы (7)


Нет, это не гарантируется, и реализация может законно присвоить ноль операнду lvalue для delete.

Бьярн Страуструп надеялся, что реализации выберут это, но не многие из них.

http://www.stroustrup.com/bs_faq2.html#delete-zero

person CB Bailey    schedule 15.02.2011
comment
Может ли он назначить любой другой адрес, кроме нуля? - person sharptooth; 15.02.2011
comment
это практически бесполезно, так как все копии указателя останутся без изменений ... - person Matthieu M.; 15.02.2011
comment
@MatthieuM .: Я не говорю, что согласен с Бьярном в этом вопросе. stackoverflow.com/questions/1265666/ - person CB Bailey; 15.02.2011
comment
@sharptooth: Да, он может (почти) все. - person CB Bailey; 15.02.2011

Если по какой-либо причине вы хотите убедиться, что указатель переменной не изменился на delete, напишите:

delete p + 0;
person fredoverflow    schedule 15.02.2011
comment
Интересный способ, я бы сказал, удалите копию указателя, но да, это тоже сработает. - person CashCow; 15.02.2011

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

person Ken Wayne VanderLinde    schedule 15.02.2011

Подпись глобального оператора delete, как того требует стандарт 3.7.3.2/2:

Каждая функция освобождения должна возвращать void, а ее первый параметр должен быть void *.

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

person Erik    schedule 15.02.2011
comment
Оператор функции delete не является оператором удаления. Сигнатура этой функции не имеет ничего общего с тем, может ли оператор изменять значение. (Эта функция, конечно, не может сама изменять какое-либо значение. Она даже не может принимать указатель по ссылке или указатель на указатель, поскольку у нее нет информации о типе из исходного указателя.) - person Fred Nurk; 15.02.2011

Подумайте, как бы вы могли проверить или полагаться на любой ответ «да» или «нет»? Вы не можете. Или вы можете, но результатом этой проверки (кроме нулевого указателя) будет Undefined Behavior.

Вы не можете проверить ненулевое значение после delete, поэтому в целом вопрос бессмысленный.

Кроме того, аргумент delete может быть выражением rvalue, поэтому вопрос не имеет смысла.

Ура & hth.,

person Cheers and hth. - Alf    schedule 15.02.2011
comment
Конечно, вы можете проверить, посмотрев на значения байтов в памяти раньше, запомнив их и посмотрев снова после. Верно, что эти байты могут быть представлением значения указателя до и (скажем) представлением ловушки после, поэтому в некотором смысле нет значения после, о котором мы можем спросить, изменилось ли оно ?. Но имеет смысл спросить о выходе, например, char *x = 0; char one = *(char*)(&x); delete x; char two = *(char*)(&x); std::cout << (one == two);. Если он выводит false, я думаю, будет справедливо сказать, что что-то в x изменилось, если не точно значение указателя. - person Steve Jessop; 15.02.2011
comment
(На самом деле, я думаю, мне там нужен char *x = new char;, поскольку нулевые указатели - это особый случай). Кроме того, тот факт, что аргумент delete может быть rvalue, не имеет прямого отношения к тому, что происходит, когда это lvalue. Конечно, разработчикам может быть предложено, что выполнение разных действий в этих двух случаях будет ненужным усилием. - person Steve Jessop; 15.02.2011
comment
@Steve: обычно нецелесообразно сериализовать значения указателя (например, хранить их в файлах), но можно предположить, что значение указателя в структуре POD и побитовое сравнение его с более ранней копией. Я думаю, что это довольно маргинальный случай. так что в общем бессмысленно. - person Cheers and hth. - Alf; 15.02.2011
comment
В общем смысле, за исключением случаев, когда это имеет смысл в том смысле, что влияет на поведение строго соответствующей программы? Конечно, это не строго соответствующая программа, которую мы с вами хотим написать. Честно говоря, я думаю, что ваша надежда на то, что ваш ответ поможет, безнадежна ;-) - person Steve Jessop; 15.02.2011
comment
@ Cheersandhth.-Alf: как аргумент для удаления может быть выражением rvalue? Не могли бы вы привести пример? - person Destructor; 15.09.2015
comment
@PravasiMeet: Самый простой реалистичный пример, вероятно, delete foo(), где foo - функция, которая обслуживает значение указателя. Бьярне Страуструп, создатель языка, также приводит несколько примеров в своем пункте часто задаваемых вопросов об этом. - person Cheers and hth. - Alf; 15.09.2015
comment
@anonymous downvoter: объясните, что вы, скорее всего, в полном незнании, считали здесь неправильным. очень антисоциально вводить других в заблуждение, голосуя против таких хороших ответов, как этот. - person Cheers and hth. - Alf; 15.09.2015

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

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

Конечно, вы можете заставить сборщик мусора выполнить «удаление» непосредственно перед удалением памяти, на которую он указывал.

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

person CashCow    schedule 15.02.2011

Это важный вопрос! Я видел, что Visual Studio 2017 изменила значение указателя после «удаления». Это вызвало проблему, потому что я использовал инструмент отслеживания памяти. Инструмент собирал указатели после каждого оператора new и проверял их после удаления. Псевдокод:

Data* New(const size_t count)
{
    Data* const ptr(new Data[count]);
    #ifdef TEST_MODE
    DebugMemory.Collect(ptr);
    #endif
    return ptr;
}

void Delete(Data* const ptr)
{
    delete[] ptr;
    #ifdef TEST_MODE
    DebugMemory.Test(ptr);
    #endif
}

Этот код хорошо работает в Visual Studio 2008, но не работает в Visual Studio 2017, поэтому я изменил порядок операций во второй функции.

Однако вопрос хороший, и проблема существует. Об этом должны знать опытные инженеры.

person Łukasz Gotszald    schedule 28.07.2018