Ответ 1
Может ли доступ к данным из-за ожидаемой могилы?
Это технически известно как Undefined Поведение. Не удивляйтесь, если он предлагает вам банку с пивом.
Я написал простую, трудную игру с тетрисом с каждым блоком в качестве экземпляра одноблочного класса.
class SingleBlock
{
public:
SingleBlock(int, int);
~SingleBlock();
int x;
int y;
SingleBlock *next;
};
class MultiBlock
{
public:
MultiBlock(int, int);
SingleBlock *c, *d, *e, *f;
};
SingleBlock::SingleBlock(int a, int b)
{
x = a;
y = b;
}
SingleBlock::~SingleBlock()
{
x = 222;
}
MultiBlock::MultiBlock(int a, int b)
{
c = new SingleBlock (a,b);
d = c->next = new SingleBlock (a+10,b);
e = d->next = new SingleBlock (a+20,b);
f = e->next = new SingleBlock (a+30,b);
}
У меня есть функция, которая сканирует полную строку и проходит через связанный список блоков, удаляющих соответствующие и переназначая → следующие указатели.
SingleBlock *deleteBlock;
SingleBlock *tempBlock;
tempBlock = deleteBlock->next;
delete deleteBlock;
Игра работает, блоки удаляются правильно, и все функционирует так, как предполагается. Однако при проверке я могу получить доступ к случайным битам удаленных данных.
Если я распечатаю каждый из удаленных единичных блоков значений "x" ПОСЛЕ их удаления, некоторые из них возвращают случайный мусор (подтверждающий удаление), а некоторые из них возвращают 222, сообщая мне, хотя деструктор был вызван, данные не были фактически удалены из кучи. Многие идентичные испытания показывают, что это всегда одни и те же конкретные блоки, которые не удалены должным образом.
Результаты:
Existing Blocks:
Block: 00E927A8
Block: 00E94290
Block: 00E942B0
Block: 00E942D0
Block: 00E942F0
Block: 00E94500
Block: 00E94520
Block: 00E94540
Block: 00E94560
Block: 00E945B0
Block: 00E945D0
Block: 00E945F0
Block: 00E94610
Block: 00E94660
Block: 00E94680
Block: 00E946A0
Deleting Blocks:
Deleting ... 00E942B0, X = 15288000
Deleting ... 00E942D0, X = 15286960
Deleting ... 00E94520, X = 15286992
Deleting ... 00E94540, X = 15270296
Deleting ... 00E94560, X = 222
Deleting ... 00E945D0, X = 15270296
Deleting ... 00E945F0, X = 222
Deleting ... 00E94610, X = 222
Deleting ... 00E94660, X = 15270296
Deleting ... 00E94680, X = 222
Может ли доступ к данным из-за ожидаемой могилы?
Извините, если это немного длиннее.
Может ли доступ к данным из-за ожидаемой могилы?
Это технически известно как Undefined Поведение. Не удивляйтесь, если он предлагает вам банку с пивом.
Может ли доступ к данным из-за ожидаемой могилы?
В большинстве случаев да. Вызов delete не обнуляет память.
Обратите внимание, что поведение не определено. Используя определенные компиляторы, память может быть обнулена. Когда вы вызываете delete, происходит то, что память помечена как доступная, поэтому в следующий раз, когда кто-то сделает новый, память может быть использована.
Если вы думаете об этом, это логично - когда вы сообщаете компилятору, что вас больше не интересует память (с помощью delete), почему компьютер должен тратить время на его обнуление.
Это то, что С++ вызывает поведение undefined - вы можете получить доступ к данным, возможно, нет. В любом случае это неправильно.
Удалить ничего не удаляет - он просто отмечает, что память "свободна для повторного использования". До тех пор, пока некоторые другие вызовы выделения не будут зарезервированы и заполнят это пространство, у него будут старые данные. Однако, полагаясь на то, что это большой нет-нет, в основном, если вы удалите что-то забыть об этом.
Один из методов в этом отношении, который часто встречается в библиотеках, - это функция Delete:
template< class T > void Delete( T*& pointer )
{
delete pointer;
pointer = NULL;
}
Это предотвращает случайный доступ к недопустимой памяти.
Обратите внимание, что нормально позвонить delete NULL;
.
Память кучи похожа на кучу досок. Представьте, что вы учитель. Пока вы преподаете свой класс, доска принадлежит вам, и вы можете делать все, что хотите. Вы можете наброситься на него и переписать материал по своему усмотрению.
Когда класс закончится, и вы собираетесь покинуть комнату, нет политики, требующей, чтобы вы удалили доску - вы просто передаете доску следующему учителю, который, как правило, сможет увидеть, что вы написали вниз.
Система не очищает память при отпускании ее через delete()
. Поэтому содержимое остается доступным до тех пор, пока память не будет назначена для повторного использования и перезаписывания.
delete освобождает память, но не изменяет ее и не обнуляет. Тем не менее вы не должны получать доступ к освобожденной памяти.
После удаления объекта он не определил, что произойдет с содержимым памяти, которую он занял. Это означает, что эта память свободна для повторного использования, но для реализации не требуется перезаписывать данные, которые были там первоначально, и не нужно повторно использовать память сразу.
Вы не должны получать доступ к памяти после того, как объект ушел, но он не должен преувеличивать, что некоторые данные остаются там в такте.
Это приведет к поведению undefined и удалению освобождает память, но не инициализирует его нулем.
Если вы хотите сделать его нулевым, выполните:
SingleBlock::~SingleBlock()
{ x = y = 0 ; }
Хотя возможно, что ваша среда выполнения не сообщает об этой ошибке, используя надлежащую проверку времени выполнения, такую как Valgrind, предупредит вас об использовании памяти после ее освобождения.
Я рекомендую, чтобы, если вы пишете код с new
/delete
и необработанными указателями (а не std::make_shared()
и тому подобное), вы выполняете свои модульные тесты под Valgrind, чтобы хотя бы иметь возможность обнаружить такие ошибки.
Ну, я тоже долго об этом задумывался, и я попытался провести несколько тестов, чтобы лучше понять, что происходит под капотом. Стандартный ответ заключается в том, что после вызова delete вы не должны ожидать ничего хорошего от доступа к этому месту памяти. Однако этого мне не показалось. Что на самом деле происходит при вызове delete (ptr)? Вот что я нашел. Я использую g++ на Ubuntu 16.04, поэтому это может сыграть роль в результатах.
То, что я впервые ожидал при использовании оператора delete, заключалось в том, что освобожденная память будет возвращена системе для использования в других процессах. Позвольте мне сказать , что этого не происходит при любых обстоятельствах, которые я пробовал.
Память, выпущенная с удалением, по-прежнему, по-видимому, выделяется программе, в которой она сначала назначила ее новым. Я пробовал, и после вызова delete нет уменьшения использования памяти. У меня было программное обеспечение, которое запустило около 30 МБ списков через новые вызовы, а затем выпустило их с последующими вызовами на удаление. Случилось так, что, смотря на монитор системы во время работы программы, даже на долгий сон после звонков на удаление, потребление памяти моя программа была такой же. Никакого снижения! Это означает, что удаление не освобождает память системы.
На самом деле, похоже, что память, выделенная программой, является его вечной! Тем не менее, дело в том, что при освобождении памяти память может снова использоваться одной программой без необходимости выделять ее. Я попытался выделить 15 МБ, освободив их, а затем выделив еще 15 МБ данных, а программа никогда не использовала 30 МБ. Системный монитор всегда показывал его около 15 МБ. То, что я делал по отношению к предыдущему тесту, заключалось в том, чтобы изменить порядок, в котором все произошло: половина распределения, половина освобождения, другая половина распределения.
Таким образом, память, используемая программой, может увеличиваться, но никогда не сжиматься. Я думал, что, возможно, память действительно будет выпущена для других процессов в критических ситуациях, например, когда больше нет памяти. В конце концов, какой смысл это сделать, чтобы программа сохраняла свою собственную память навсегда, когда другие процессы просят об этом? Поэтому я снова выделил 30 МБ и , освободив их. Я запустил memtester
с такой физической памятью, как я. Я ожидал увидеть, что мое программное обеспечение передает свою память memtester. Но догадайся, этого не случилось!
Я составил короткий скринкаст, который показывает, что в действии:
Чтобы быть на 100% честным, была ситуация, в которой что-то случилось. Когда я попробовал memtester с большей доступной физической памятью в середине процесса освобождения моей программы, память, используемая моей программой, упала примерно до 3 МБ. Процесс memtester был убит автоматически, хотя и что произошло еще более удивительно! Использование памяти моей программы увеличилось с каждым вызовом на удаление! Это было похоже на то, что Ubuntu восстанавливал всю память после инцидента memtester.
Взято из http://www.thecrowned.org/c-delete-operator-really-frees-memory
Он еще не будет обнулять/менять память... но в какой-то момент, коврик будет вытаскиваться из-под ваших ног.
Нет, это, конечно, не предсказуемо: это зависит от того, как быстро распределяется память/освобождение памяти.
Да, этого можно ожидать время от времени. В то время как new
резервирует место для данных, delete
просто делает недействительным указатель, созданный с помощью new
, позволяя записывать данные в ранее зарезервированных местах; он не обязательно удаляет данные. Однако вы не должны полагаться на это поведение, поскольку данные в этих местах могут меняться в любое время, что может привести к неправильной работе вашей программы. Вот почему после использования delete
на указателе (или delete[]
на массиве, выделенном с помощью new[]
), вам следует назначить ему NULL, чтобы вы не могли вмешиваться в недопустимый указатель, если вы не будете выделите память с помощью new
или new[]
перед повторным использованием этого указателя.