В книге на C ++, которую я читал, говорится, что когда указатель удаляется с помощью delete
оператора, память в том месте, на которое он указывает, «освобождается» и может быть перезаписана. В нем также указано, что указатель будет продолжать указывать на то же место, пока он не будет переназначен или установлен на NULL
.
Однако в Visual Studio 2012; похоже, что это не так!
Пример:
#include <iostream>
using namespace std;
int main()
{
int* ptr = new int;
cout << "ptr = " << ptr << endl;
delete ptr;
cout << "ptr = " << ptr << endl;
system("pause");
return 0;
}
Когда я компилирую и запускаю эту программу, я получаю следующий результат:
ptr = 0050BC10
ptr = 00008123
Press any key to continue....
Очевидно, что адрес, на который указывает указатель, изменяется при вызове удаления!
Почему это происходит? Связано ли это конкретно с Visual Studio?
И если delete в любом случае может изменить адрес, на который он указывает, почему бы delete автоматически не установить указатель NULL
вместо некоторого случайного адреса?
Ответы:
Я заметил, что адрес, хранящийся в,
ptr
всегда перезаписывался00008123
...Это казалось странным, поэтому я немного покопался и нашел это сообщение в блоге Microsoft, содержащее раздел, посвященный «Автоматическая очистка указателя при удалении объектов C ++».
Он не только объясняет, что Visual Studio делает с указателем после его удаления, но и объясняет, почему они решили НЕ устанавливать его
NULL
автоматически!Эта «функция» включена как часть настройки «Проверки SDL». Чтобы включить / отключить его, перейдите в: ПРОЕКТ -> Свойства -> Свойства конфигурации -> C / C ++ -> Общие -> Проверки SDL
Чтобы подтвердить это:
Изменение этого параметра и повторный запуск того же кода дает следующий результат:
«функция» заключена в кавычки, потому что в случае, когда у вас есть два указателя на одно и то же место, при вызове удаления будет очищен только ОДИН из них. Другой останется указывающим на недопустимое местоположение ...
ОБНОВИТЬ:
Проработав еще 5 лет в программировании на C ++, я понял, что вся эта проблема в основном спорный. Если вы программист на C ++ и все еще используете
new
иdelete
управляете необработанными указателями вместо использования интеллектуальных указателей (которые позволяют обойти всю эту проблему), вы можете подумать об изменении карьеры, чтобы стать программистом на C. ;)источник
0x8123
вместо0
. Указатель по-прежнему недействителен, но вызывает исключение при попытке разыменовать его (хорошо), и он не проходит проверки на NULL (также хорошо, потому что не делать этого - ошибка). Где место вредным привычкам? Это действительно то, что помогает вам отлаживать.Вы видите побочные эффекты
/sdl
опции компиляции. Включенный по умолчанию для проектов VS2015, он позволяет выполнять дополнительные проверки безопасности помимо тех, которые предоставляет / gs. Используйте «Проект»> «Свойства»> «C / C ++»> «Общие»> «Проверки SDL», чтобы изменить его.Цитата из статьи MSDN :
Имейте в виду, что установка удаленных указателей на NULL - плохая практика при использовании MSVC. Это сводит на нет помощь, которую вы получаете как от Debug Heap, так и от этого параметра / sdl, вы больше не можете обнаруживать недопустимые вызовы free / delete в своей программе.
источник
delete
один, Visual Studio оставит второй указатель, указывающий на его исходное местоположение, которое теперь недействительно.Это определенно вводящая в заблуждение информация.
Это явно находится в пределах языковых спецификаций.
ptr
недействителен после вызоваdelete
. Использованиеptr
после того, как это былоdelete
d, является причиной неопределенного поведения. Не делай этого. Среда выполнения может делать все, что захочет,ptr
после вызоваdelete
.Изменение значения указателя на любое старое значение находится в пределах спецификации языка. Что касается изменения его на NULL, я бы сказал, что это было бы плохо. Программа вела бы себя более разумно, если бы значение указателя было установлено в NULL. Однако это скроет проблему. Когда программа скомпилирована с другими настройками оптимизации или перенесена в другую среду, проблема, скорее всего, проявится в самый неподходящий момент.
источник
delete
двойной вызов указателя не вызовет проблемы. Это определенно не хорошо.NULL
но определенно находится за пределами адресного пространства процесса, обнаружит больше случаев, чем две альтернативы. Если оставить его висящим, это не обязательно вызовет segfault, если он используется после освобождения; установка его наNULL
не вызовет segfault, если онdelete
снова d.В общем, даже чтение (как вы это делаете выше, обратите внимание: это отличается от разыменования) значений недопустимых указателей (указатель становится недействительным, например, когда вы
delete
это делаете ) является поведением, определяемым реализацией. Это было представлено в CWG № 1438 . Смотрите также здесь .Обратите внимание, что до этого чтение значений недействительных указателей было неопределенным поведением, поэтому то, что у вас было выше, было бы неопределенным поведением, что означает, что все могло случиться.
источник
[basic.stc.dynamic.deallocation]
: «Если аргумент, переданный функции освобождения в стандартной библиотеке, является указателем, который не является значением нулевого указателя, функция освобождения освобождает память, на которую ссылается указатель, делая недействительными все указатели, относящиеся к любому часть освобожденного хранилища »и правило[conv.lval]
(раздел 4.1), которое гласит, что чтение (преобразование lvalue-> rvalue) любого недопустимого значения указателя является поведением, определяемым реализацией.Я считаю, что вы используете какой-то режим отладки, и VS пытается перенаправить ваш указатель на какое-то известное место, чтобы можно было отследить дальнейшую попытку разыменования и сообщить о нем. Попробуйте скомпилировать / запустить ту же программу в режиме выпуска.
Указатели внутри обычно не меняются
delete
ради эффективности и во избежание ложного представления о безопасности. Установка указателя удаления на предопределенное значение не принесет пользы в большинстве сложных сценариев, поскольку удаляемый указатель, вероятно, будет только одним из нескольких, указывающих на это место.На самом деле, чем больше я думаю об этом, тем больше я нахожу, что VS, как обычно, виноват в этом. Что делать, если указатель постоянный? Это все еще изменит это?
источник
После удаления указателя память, на которую он указывает, может оставаться действительной. Чтобы проявить эту ошибку, значение указателя устанавливается на очевидное значение. Это действительно помогает процессу отладки. Если значение было установлено
NULL
, оно может никогда не проявиться как потенциальная ошибка в потоке программы. Таким образом, он может скрыть ошибку, когда вы позже протестируете ееNULL
.Другой момент заключается в том, что некоторый оптимизатор времени выполнения может проверять это значение и изменять его результаты.
В прежние времена MS устанавливала значение на
0xcfffffff
.источник