C++ delete-он удаляет мои объекты, но я все еще могу получить доступ к данным?

Я написал простую, рабочую игру tetris с каждым блоком в качестве экземпляра класса singleblock.

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

имеет доступ к данным из-за пределов могила ожидается?

Извините, если это немного долго наматывается.

13 ответов


ожидается ли доступ к данным из-за могилы?

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


ожидается ли доступ к данным из-за могилы?

в большинстве случаев, да. Вызов delete не обнуляет память.

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

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


Это то, что C++ называет неопределенным поведением - вы можете получить доступ к данным, а можете и нет. В любом случае, это неправильно.


Delete ничего не удаляет - он просто помечает память как "свободную для повторного использования". Пока некоторые другие распределения вызовов резервов и заполняет это пространство будет иметь старые данные. Однако полагаться на это-большое нет-нет, в основном, если вы удалите что-то, забудьте об этом.

одной из практик в этом отношении, которая часто встречается в библиотеках, является функция Delete:

template< class T > void Delete( T*& pointer )
{
    delete pointer;
    pointer = NULL;
}

Это предотвращает нас от случайного доступа недействительными память.

обратите внимание, что это совершенно нормально называть delete NULL;.


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

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


система не очищает память, когда вы освобождаете ее через delete(). Поэтому содержимое остается доступным до тех пор, пока память не будет назначена для повторного использования и перезаписи.


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


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

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


Он еще не будет обнулять/изменять память... но в какой-то момент ковер у тебя из-под ног выдернут.

нет, это, конечно, не предсказуемо: это зависит от того, как быстро выделение/освобождение памяти сбивается.


Да, это можно ожидать в разы. Тогда как new резервирует место для данных,delete просто аннулирует указатель, созданный с помощью new, что позволяет записывать данные в ранее зарезервированных местах; это не обязательно удалить данные. Однако вы не должны полагаться на это поведение, потому что данные в этих местах могут измениться в любое время, возможно, заставляя вашу программу плохо себя вести. Вот почему после использования delete на указатель (или delete[] на массиве, выделенном new[]), вы должны назначить ему NULL, чтобы вы не могли вмешиваться в недопустимый указатель, предполагая, что вы не будете выделять память с помощью new или new[] перед использованием этого указателя.


это приведет к неопределенному поведению и удалению освобождает память, он не повторно инициализирует ее с нулем .

Если вы хотите сделать его нулевым, то сделайте:

SingleBlock::~SingleBlock()

{    x = y = 0 ; }

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

Я рекомендую, если вы пишете код с new/delete и сырые указатели (а не std::make_shared() и аналогично), что вы выполняете свои модульные тесты под Valgrind, чтобы по крайней мере иметь шанс обнаружить такие ошибки.


Ну, я тоже задавался этим вопросом довольно долго, и я попытался запустить некоторые тесты, чтобы лучше понять, что происходит под капотом. Стандартный ответ заключается в том, что после вызова удалить вы не должны ожидать ничего хорошего от доступа к этому месту памяти. Однако этого мне показалось недостаточно. Что на самом деле происходит при вызове удалить (ptr)? Вот что я нашел. Я использую g++ на Ubuntu 16.04, поэтому это может сыграть роль в результаты.

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

память удалить по-прежнему, похоже, выделяется программе, которую он сначала выделил с новая. Я пробовал, и нет никакого уменьшения использования памяти после вызова удалить. У меня было программное обеспечение, которое allcated вокруг 30MB списков через новая звонки, а затем выпустил их с последующим удалить звонки. Случилось так, что, глядя на Системный монитор во время работы программы, даже после долгого сна удалить звонки, потребление памяти моей программой было одинаковым. Нет уменьшения! Это значит, что удалить не освобождает память системы.

на самом деле, это выглядит как память, выделенная программой его навсегда! Однако дело в том, что в случае освобождения память может быть снова использована той же программой без необходимости выделять больше. Я попытался выделить 15MB, освободив их, а затем выделив еще 15MB данных после, и программа никогда не использовала 30MB. Системный монитор всегда показывал его около 15MB. Что я сделал в отношении предыдущего теста, так это просто изменил порядок, в котором все происходило: половина распределения, половина освобождения, другая половина распределение.

и по-видимому, память, используемая программой, может увеличиваться, но никогда не сжиматься. Я подумал, что, возможно, память действительно будет освобождена для других процессов в критических ситуациях, например, когда памяти больше нет. В конце концов, какой смысл позволять программе вечно хранить собственную память, когда другие процессы просят об этом? Поэтому я снова выделил 30MB и при освобождении их Я memtester столько физической память я могу. Я ожидал, что мое программное обеспечение передаст свою память memtester. Но, похоже, этого не произошло!

Я составил короткий скринкаст, который показывает вещь в действии:

Delete example memory

чтобы быть на 100% честным, была ситуация, в которой что-то случилось. Когда я попробовал memtester с более чем доступной физической памятью в середине процесса освобождения моей программы, память, используемая моей программой за примерно 3МБ. Автоматически, хотя убил процесс memtester был, и то, что произошло еще более удивительное! Использование памяти моей программы увеличивается с каждым вызовом delete! Это было так же, как если бы Ubuntu восстанавливал всю свою память после инцидента с memtester.

взяты из http://www.thecrowned.org/c-delete-operator-really-frees-memory